예전에는 php 로 서버구성을 많이 하였는데, 요즘들어 개인적으로 Python 으로 서버를 돌려야 하는 일이 잦아졌습니다.

Tensorflow 로 학습한 AI 모델을 서비스 하기 위해서도 그렇고 - 물론 지금은 Tensorflow 가 Python 이외에도 지원합니다 - 개발 편의를 위해서 그렇기도 합니다.




DJANGO 와 WSGI 와 NGINX


Python 으로 웹서버(Server Side Script)를 개발하려고 하면 항상 나오는게 WSGI 입니다.

이 녀석은 Web Server Gateway Interface 의 약자이며, 예전에 cgi 그리고 php 의 fpm 과 비슷한 Gateway Interface 중 하나입니다. 당연히 Python 에서 사용하는 용어이며 Python 의 표준 Gateway Interface 입니다.

그리고 uWSGI 는 이 WSGI 을 Implementation 한 것입니다.




Python 의 웹 서비스 구조




일단 결론부터 말씀드리면 이렇습니다.

Python 은 다음 방법 중 하나를 통해서 클라이언트에게 서비스 할 수 있습니다.


  1. uWSGI 를 이용하여 포트로 서비스하는 방법
  2. uWSGI 를 이용하여 소켓으로 서비스하는 방법
  3. uWSGI 를 이용하여 Unix Socket 으로 서비스하는 방법
은 대표적인 것일 뿐, 실제로 uwsgi 의 help 페이지를 보면, 훨씬 더 많은 binding 방법들이 있습니다.

이론적인 것들을 설명하려고 하였으나, 분량이 너무 많아서 실무적인 부분만 다루도록 하겠습니다. -_-;




uWSGI 설치하기




Django 는 설치했다는 가정하에, uWSGI 를 설치합니다.


pip install uwsgi


설치가 완료되었다면, 샘플파일을 만들어 봅니다.



# test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"] # python3
    #return ["Hello World"] # python2

이제 uWSGI 를 실행해 봅니다.

uwsgi --http :8080 --wsgi-file test.py



그리고나서 8080 포트로 접속하면 Hello World 라는 문구를 볼수 있습니다.






Django 를 uWSGI 로 서비스하기




Django 의 프로젝트 폴더안에 있는 프로젝트명의 폴더를 보면, wsgi.py 파일이 들어 있을 것입니다.

이것을 uWSGI 로 서비스 하려면 다음과 같이 하면 됩니다.

저의 경우 프로젝트명이 html 입니다.

프로젝트의 전체 경로는 /home/crystalcube/html 이며 wsgi.py 파일은 /home/crystalcube/html/html/wsgi.py 에 위치합니다.




[crystalcube@host /home/crystalcube/html] uwsgi --http :8080 --module html.wsgi



모듈의 경로는 ./html/wsgi.py 를 넣는것이 아니라 html.wsgi 로 넣습니다. 파이썬에서 자주 볼 수 있는 표현방식이지요.
이제 8080 포트로 접속을 하면 Django 로 구현했던 페이지를 볼 수 있습니다.

지금 서비스되는 구조를 정리하자면, 아래와 같습니다.

[클라이언트] -> [uWSGI] -> [Django]





Nginx 를 연동하기




일반적으로 안정적이고 빠른 웹서비스를 위해서, Nginx 앞단에 붙입니다.

최종적인 구조는 다음과 같을 것입니다.


[클라이언트] -> [Nginx] -> [unix Socket] -> [uWSGI] -> [Django]



uwsgi_params 라는 이름으로 파일을 하나 만듧니다.


uwsgi_param  QUERY_STRING       $query_string;

uwsgi_param  REQUEST_METHOD     $request_method;

uwsgi_param  CONTENT_TYPE       $content_type;

uwsgi_param  CONTENT_LENGTH     $content_length;


uwsgi_param  REQUEST_URI        $request_uri;

uwsgi_param  PATH_INFO          $document_uri;

uwsgi_param  DOCUMENT_ROOT      $document_root;

uwsgi_param  SERVER_PROTOCOL    $server_protocol;

uwsgi_param  REQUEST_SCHEME     $scheme;

uwsgi_param  HTTPS              $https if_not_empty;


uwsgi_param  REMOTE_ADDR        $remote_addr;

uwsgi_param  REMOTE_PORT        $remote_port;

uwsgi_param  SERVER_PORT        $server_port;

uwsgi_param  SERVER_NAME        $server_name;



nginx.conf 파일도 하나 만듧니다.


upstream django {

    server unix:///sock/django.sock;

}


server {

    listen 8080;

    charset utf-8;

    server_name _;


    root /home/crystalcube;


    location /images {

        alias /home/data;

    }



    location /music {

        root /sounds;

        expires 1M;

        add_header Cache-Control "public";

    }



    location / {

        uwsgi_path django;

        include /path/to/uwsgi_params;

    }

}



이제 Nginx 의 config.d 폴더에 위의 nginx.conf 를 넣고 nginx 를 재시작 합니다.



nginx.conf 파일을 대략적으로 설명하자면,


server unix:///sock/django.sock;

uwsgi 를 port 가 아닌 unix socket 으로 실행할 것입니다.

그것이 성능면에서 우월합니다. 그리고 그 소켓의 위치는 /sock/django.sock 입니다.


root /home/crystalcube;

이 서비스의 root 경로입니다.

보안상 root 를 지정해 주는것이 좋습니다. 밖의 디렉토리/파일에 접근을 제한하기 위함입니다.



    location /images {

        alias /home/data;

    }


    location /music {

        root /sounds;

        expires 1M;

        add_header Cache-Control "public";

    }


알다시피 Django 는 static 파일에 대해서 서비스를 지원하지 않습니다.

Nginx 에서 static 파일을 serve 할 수 있는 방법이 크게 두가지인데, 하나가 alias 를 이용한 것이고 다른 하나가 root 를 이용한 것입니다.

두개의 차이를 설명하기 위해서 images 와 music 이라는 location 을 만들었습니다.


alias 는 위에 location 에 정의된 경로(/images)를 host 의 alias 경로로 변환한다고 생각하시면 됩니다.

예를들어 http://localhost:8080/images/1.jpg 인 경우, host 의 /home/data/1.jpg 에서 파일을 찾게 됩니다.

server 블록에 있는 root 에서 /home/crystalcube 라고 지정했는데, 이 부분은 alias 에서는 무시됩니다.


/music 을 살펴보면, root 를 sounds 로 지정하였습니다.

root 의 경우에는 location 에 정의된 경로(/music)의 root 를 지정한다고 생각하시면 됩니다.

다시말해 /music 이라는 url 에 대한 root 경로가 /sounds 이라는 뜻입니다.

그런데 이 server 블록에서 root 는 /home/crystalcube 이죠.

그러므로 location 이 music 인 경우, 이것의 최종적인 root 경로는 /home/crystalcube/sounds 가 되는 것입니다.

이건 어디까지나 root 의 경로이므로 서비스 경로는 /home/crystalcube/sounds/music 이 됩니다.


예를들어 http://localhost:8080/music/1.mp3 의 host 경로는 /home/crystalcube/sounds/music/1.mp3 가 됩니다.


요컨데 alias 의 값은 server의 root 는 무시한 절대적인 경로로 대체됩니다.

root 는 'server 의 root 경로' + 'root 경로' + 'location 경로' 가 됩니다.


expires 와 add_header 는 Cache 를 넣기 위한 것입니다.

웬만해서 변할일이 없는 static 파일은 캐시를 넣어 주는것이 좋겠지요.




이제 uWSGI 가 Unix Socket 을 통해서 Django 를 서비스 하도록 해 봅시다.

nginx 를 다시 실행해 줍니다.


sudo systemctl reload-daemon

sudo systemctl restart nginx


그리고 uWSGI 를 실행합니다.


uwsgi --socket /sock/django.sock --module html.wsgi --chmod-socket=666



제대로 동작하고 있는지 8080 포트로 접속을 해 봅니다.

서비스 구조는 앞서 말씀드린대로 다음과 같습니다.


[클라이언트(브라우저)] -> [Nginx] -> [Unix Socket] -> [uWSGI] -> [Django]







uWSGI 서비스 등록하기



먼서 uWSGI 를 서비스에 등록하기 위해서, 커맨드 라인으로 실행하던 것을 ini 파일로 만드는 것이 편합니다.

uwsgi.ini 라는 이름으로 파일을 만듧니다.


[uwsgi]

chdir        = /home/crystalcube/html

module       = html.wsgi


master       = true

processes    = 10

socket       = /sock/django.sock

chmod-socket = 666

vacuum       = true



테스트를 위해서 이것을 실행하려면 다음과 같이 하면 됩니다.


uwsgi --ini ./html/uwsgi.ini



이제 이것을 system daemon 으로 등록합니다.

저의 경우 /etc/systemd/system/django.service 라는 이름으로 했습니다.(대충대충..)


[Unit]

Description=Crystalcube Sample

After=network.target


[Service]

User=crystalcube

Group=crystalcube

Environment=LOG_PATH=/home/crystalcube/logs # Just sample

WorkingDirectory=/home/crystalcube/html

ExecStart=/usr/bin/uwsgi --ini ./html/uwsgi.ini


[Install]

WantedBy=multi-user.target



sudo systemctl daemon-reload

sudo systemctl start django.service



이제 8080 포트를 통해서 접근하면, 정상적으로 동작할 것입니다.

시스템이 재부팅 될때마다 자동으로 실행하려면, 시스템 데몬에 enabled 로 등록해 주시면 됩니다.





일일이 설명한다는게 여간 힘든게 아니네요.

이번 글은 날림으로 작성하였습니다. ㅎ...







'Python' 카테고리의 다른 글

Python 시작하기  (0) 2011.02.12