Site Deployment (Nginx)

Django - Site Deployment

Django 웹 프로젝트가 완성되었으면, 이를 실제 웹 서버에 올리는 일을 하게 된다. 이러한 서버 Deployment는 서버 OS가 무엇인가에 따라, 어떤 WSGI 서버를 사용하는가에 따라, 그리고 어떤 웹 서버를 사용하는가에 따라 다양한 방법이 있다.

Django 프로젝트는 보통 Apache/mod_wsgi, Gunicorn, uWSGI 등을 사용하여 Deploy 한다. 여기서는 Django 서버에 일반적으로 많이 사용되는 리눅스 OS와 Nginx 웹 서버 그리고 Gunicorn WSGI 서버을 사용하는 방법을 소개한다. 리눅스로 여기서는 Centos 7을 사용하며, Python은 버전 3.4를 사용한다고 가정하자.

아래 설명은 Deployment를 하는 순서대로 한 것이라기 보다는 각 컴포넌트를 Deploy하는 근거 및 과정을 설명한 것으로 실제 Deployment 순서는 약간 다를 수 있다. 아래 설명 중 # 프롬프트는 root 권한 실행을 의미하고, $는 일반 유저 실행을 의미한다.

1. Python 셋업

Centos 7에 파이썬 3.4가 설치되어 있지 않다면, 아래와 같이 Python 3.4를 설치한다. Python 3.4가 설치된 후, Python 가상환경을 생성하고(권장사항), pip을 통해 Django를 설치한다.

  1. Centos에서 Python 3.4는 아래 명령을 통해 EPEL Repository로부터 설치할 수 있다.
    # yum install epel-release
    # yum install python34
    
  2. Python 가상환경은 pyvenv를 실행하여 새 가상환경을 생성한다 (여기서는 alex 라는 User를 사용하자). (주: 아래는 Python 3.4 배포판의 pip 버그로 인해 수동 설치하는 절차임)
    # su - alex
    $ pyvenv --without-pip venv1
    $
    $ . venv1/bin/activate
    (venv1)$ curl https://bootstrap.pypa.io/get-pip.py | python3.4   #pip 수동 설치
    (venv1)$ deactivate
    $
    
  3. 가상환경이 생성되었으면, pip을 사용하여 Django 개발시 사용했던 패키지들을 설치한다. 개발서버의 가상환경을 그대로 Deploy하기 위해, pip과 requirements.txt를 사용하면 편리하다. (주: 아티클 참조)
    $ . venv1/bin/activate
    (venv1)$ pip install -r requirements.txt
    

2. Python 프로젝트 소스 복사

Python 프로젝트 소스코드를 웹 디렉토리에 복사한다. 개발서버의 소스코드를 Git이나 SVN 같은 소스 컨트롤 시스템에 체크인하고 이를 다운 받는 방식을 사용할 수 있으며, 또한 FTP 등을 통해 서버에 압축파일을 전송할 수도 있다. 본 Deployment에서는 소스코드를 /var/www/myweb 에 복사하였다고 가정하자. 복사후 /var/www/myweb 폴더와 서브폴더를 alex 가 사용할 수 있도록 권한을 설정한다.

# cd /var/www
# chown -R alex:alex myweb

소스코드가 복사되었으면, 아래와 같이 Django의 Development Server를 사용하여 웹 프로그램이 제대로 동작하는지 체크할 수 있다. 다른 세션에서 "curl http://localhost:8000" 을 실행해보면 HTML 코드가 리턴됨을 볼 수 있다.

$ . venv1/bin/activate
(venv1) /var/www/myweb $ ./manage.py runserver

Django의 Development Server는 개발을 위한 간단한 웹 서비스를 제공하지만, 단일 쓰레드 프로세스이기 때문에 Production에서 사용할 수 없다. 따라서 Production Server에서는 아래의 WSGI 서버를 사용한다.

3. DNS 셋팅

도메인을 Production 서버로 가리키기 위해서 DNS 설정을 해주어야 한다. DNS 설정은 일반적으로 해당 도메인을 구입한 도메인 업체(예: 가비아, NameCheap, GoDaddy 등) 웹 사이트를 방문하여 도메인 호스명과 IP 주소를 매핑시켜주면 된다. 여기서는 가상의 도메인이름으로 mydomain.com 을 사용한다고 가정한다.

Namecheap
저렴한 도메인 구입은 Namecheap 을 권장...

4. WSGI 서버

WSGI (Web Server Gateway Interface)는 웹 서버와 웹 어플리케이션 사이에 어떻게 통신할 것인가에 대한 규약으로서 Python에서 표준으로 사용된다. WSGI 서버는 Django에서 일차적으로 사용하는 Deployment 플랫폼인데, 일반적으로 많이 사용되는 WSGI 서버로는 uWSGI, Gunicorn과 Apache/mod_wsgi 등이 있다. uWSGI 서버는 고성능 서버로서 다양한 옵션들을 제공하고 있으며, Gunicorn은 보통 수준의 성능을 제공하지만 설치와 관리가 간단해서 많이 사용된다. 여기서는 Gunicorn을 살펴본다. Apache를 사용하는 방법은 Apache Django Deployment 아티클을 참고한다.

Gunicorn (Green Unicorn) 은 유닉스 계열을 위한 WSGI 서버로서 설치 및 사용이 편리한 장점이 있다. Gunicorn은 아래와 같이 가상환경에서 pip을 사용하여 간단히 설치할 수 있다.

(venv1) $ pip install gunicorn

Gunicorn 설치 후, 아래와 같이 Django 프로젝트 루트 폴더에서 Gunicorn 을 실행하면 Gunicorn 웹 서비스가 시작된다 (Django 프로젝트명은 myweb). 다른 세션에서 "curl http://localhost:8000/" 을 실행해보면 HTML 코드가 리턴됨을 볼 수 있다.

(venv1) /var/www/myweb $ gunicorn myweb.wsgi

만약 도메인명으로 외부에서 접속하고자 한다면, 아래와 같이 "도메인명:포트"로 바인딩을 설정해 주면된다. 즉, 도메인이 mydomain.com이고 80 포트로 접속한다고 가정하면 아래와 같은 바인딩 설정을 하게된다.

(venv1) /var/www/myweb $ gunicorn myweb.wsgi --bind mydomain.com:80

이러한 방식으로 외부에서 웹 사이트 접속이 가능하지만, Gunicorn 서버는 독립적인 웹 서버로서의 기능이 약하고 DDOS 공격에 취약한 단점이 있으므로, 일반적으로 Nginx나 Apache 같은 독립적인 웹 서버와 함께 사용할 것을 권장한다.

5. Nginx 웹서버 셋업

Nginx는 웹 서버로서 Apache Server보다 상대적으로 가벼운(lightweight) 웹 서버이다. Nginx는 정적 웹 컨텐츠 즉 HTML, 이미지, CSS, JavaScript 등을 처리해주는 웹 서버이다. Python을 이용한 동적 웹 페이지 핸들링은 Gunicorn, uWSGI 같은 WSGI 서버를 통해 하게 된다. 즉, 웹 클라이언트는 Nginx 웹서버를 통해 통신하며, Nginx는 WSGI 서버의 Proxy Server로 기능하면서 동적 웹페이지를 WSGI 서버에 의뢰하고 HTML 결과를 다시 클라이언트에 전달하는 역활을 한다.

Nginx를 설치하고 도메인 설정을 하는 방법은 아래와 같다.

  1. EPEL Repository로부터 nginx 를 설치한다.
    # yum install epel-release
    # yum install nginx
    
  2. Firewall이 활성화되어 있는 경우, http (필요시 https도) 포트를 Open 한다.
    # firewall-cmd --permanent --zone=public --add-service=http
    # firewall-cmd --permanent --zone=public --add-service=https
    # firewall-cmd --reload
    
  3. 구성파일인 nginx.conf (/etc/nginx/nginx.conf)를 수정해서 도메인명을 매핑한다. listen port를 80으로 변경하고, server_name을 공식 도메인명으로 변경한다. location / 를 proxy_pass 를 사용하여 localhost의 8000으로 리디렉트한다. 또한, /static 으로 시작되는 정적 콘텐츠는 /var/www/myweb_static 을 가리키도록 설정하였다.
    server {
        listen       80;
        server_name  mydomain.com;
        location / {
            proxy_pass http://localhost:8000/;
        }
        location /static {
            alias /var/www/myweb_static;
        }
    }
    
  4. 위의 구성파일 설정과 함께, Centos에 적용되는 SELinux (Security-Enhanced Linux) 메카니즘에 따라 다음 두가지 셋팅을 추가하여야 한다.

    첫째, Nginx HTTP 서버가 네트워크를 통해 WSGI 서버를 엑세스하기 위하여 다음 셋팅을 on 으로 설정할 필요가 있다. 이는 proxy_pass 설정에 따라 로컬 웹사이트를 엑세스하기 위해 필요하다.

    # setsebool httpd_can_network_connect on -P
    

    둘째, Nginx 서버가 정적 컨텐츠 디렉토리를 엑세스할 수 있도록 httpd_sys_content_t를 설정한다. 아래는 Nginx가 /var/www와 서브폴더를 엑세스할 수 있도록 하는 명령이다.

    # chcon -Rt httpd_sys_content_t /var/www
    
  5. Django 프로젝트의 정적 파일들을 Nginx 서버가 핸들링하도록 하기 위해 Django에서 "manage.py collectstatic" 명령을 실행한다. collectstatic 명령은 Django 프로젝트와 각 Django App 안에 있는 Static 파일들을 settings.py 파일 안에 정의되어 있는 STATIC_ROOT 디렉토리로 옮기는 작업을 한다. 즉, settings.py 에 다음과 같이 STATIC_ROOT 가 설정되어 있을 때,
    STATIC_ROOT = '/var/www/myweb_static'
    
    아래 collectstatic 명령은 모든 정적 파일들을 /var/www/myweb_static 디렉토리에 복사해 준다.
    (venv1) /var/www/myweb $ ./manage.py collectstatic
    
  6. Nginx 서버를 시작한다. 시스템 부팅시 자동 시작을 위해 systemctl enable 명령을 실행한다.
    # systemctl start nginx
    # systemctl enable nginx
    

Nginx 서버가 실행과 더불어 WSGI 서버를 함께 시작한다. 위의 nginx.conf 에서 설정된 proxy_pass 웹 포트가 8000 이므로, 아래와 같이 실행하면 외부 웹 브라우저에서 접속해 볼 수 있다.

(venv1) /var/www/myweb $ gunicorn myweb.wsgi --bind localhost:8000

6. WSGI 서비스 관리자

실제 Production 서버에서는 Gunicorn 프로세스를 콘솔에서 Interactive로 실행하지 않고, 백그라운드에서 돌게 할 것이다. 또한, 만약 시스템이 재부팅하거나 어떤 이유로 프로세스가 중단되더라도 다시 재시작할 수 있는 기능이 있어야 할 것이다. 이러한 기능을 수행하기 위해, runit, supervisor 등과 같은 서비스 관리 유틸러티들을 활용할 수 있다. 여기서는 supervisor를 이용하여 Gunicorn 을 항상 백그라운드에서 돌게 하는 기능을 살펴보자.

Gunicorn 실행 쉘 스크립트 작성

먼저 위의 Django 프로젝트가 가상환경에 돌기 때문에, 아래와 같이 간단한 쉘 스크립트를 작성한다. (주: 가상환경이 아니라면 굳이 스크립트가 필요 없음) 이 스크립트를 Django 프로젝트 폴더 (/var/www/myweb) 에 start_myweb.sh 이라고 저장하고, chmod를 사용하여 실행파일로 만든다 ( chmod +x start_myweb.sh ).

#!/bin/bash

. /home/alex/venv1/bin/activate
cd /var/www/myweb
exec /home/alex/venv1/bin/gunicorn myweb.wsgi --bind localhost:8000
supervisor 설치

Gunicorn WSGI 프로세스를 모니터링하기 위해 supervisor를 yum을 이용해서 설치한다. 설치 후, systemctl을 이용하여 supervisord를 시작한다.

# yum install supervisor
# systemctl start supervisord
supervisor 설정

supervisor에서 어느 프로그램을 모니터링하기 위해서는 (Centos의 경우) /etc/supervisord.d/ 디렉토리 아래 해당 프로그램의 .ini 파일을 작성한다. 즉, 위의 myweb을 모니터링하기 위해 myweb.ini 를 새로 생성한다. (주: 메인 구성파일인 /etc/supervisord.conf 의 마지막 부분 [include] 섹션 참조)

# vi /etc/supervisord.d/myweb.ini

myweb.ini 에는 아래와 같은 설정이 들어간다. 가장 첫 라인에는 프로그램명(program:myweb)을 지정하고, command 는 위에서 작성한 gunicorn 실행 쉡 스크립트를 지정한다. 그외 다양한 옵션들을 선택적으로 사용할 수 있는데, user는 프로그램을 실행하는 사용자를 표시하고, autostart는 시스템 재부팅시 자동 시작을 (실제로는 supervisord가 시작될 때 자동 시작. supervisord를 enable 할 것), autorestart는 프로세스가 Crash 되었을 때 재시작 할 것을 설정한다. 기타 로그 파일의 위치 (stdout_logfile), stderr 에러도 로그 파일에 저장할지 (redirect_stderr) 등을 의미한다. 옵션에 대한 보다 자세한 정보는 supervisord.org 사이트를 참조하면 된다.

[program:myweb]
command = /var/www/myweb/start_myweb.sh
user = alex
autostart = true
autorestart = true
stdout_logfile = /var/log/myweb.log
redirect_stderr = true
environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8

myweb.ini 이 저장된 후, 아래 명령으로 해당 프로그램을 구동시킨다. 만약 프로그램이 시작되지 않으면, stdout_logfile에 지정된 log 파일을 참조하여 어떤 에러인지 체크한다. 만약 Configuration이 이미 supervisor에 갱신된 경우는 "supervisorctl start myweb"으로 재시작할 수 있다.

# supervisorctl reread
# supervisorctl update
Python 프로그래밍 실습

본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.