Ubuntu 리눅스에 Django 어플리케이션 배포하기(Nginx+Django+uWSGI)

Ubuntu 리눅스에 Django 어플리케이션 배포하기(Nginx+Django+uWSGI)

* 작업환경
OS: ubuntu 20.04 LTS
Python: Python 3.8.2
uwsgi : 2.0.18
Django: 3.0.6
Nginx : 1.17.10

1. 준비작업(Django project 만들고 django app 만들기 – 배포 테스트용)

virtualenv 로 간단한 프로젝트를 만든다. virtualenv 가 없으면, 아래 명령어로 설치한다.

$ sudo apt install python3-virtualenv

python 가상환경을 만들고 django를 설치한다.

snowfox@ubuntu:~$ virtualenv DjangoTest
snowfox@ubuntu:~$ . DjangoTest/bin/activate
(DjangoTest) snowfox@ubuntu:~$
(DjangoTest) snowfox@ubuntu:~$ pip install django

이제, Test라는 이름의 django project를 만든다.

(DjangoTest) snowfox@ubuntu:~$ cd DjangoTest
(DjangoTest) snowfox@ubuntu:~/DjangoTest$ ls
bin  lib  pyvenv.cfg
(DjangoTest) snowfox@ubuntu:~/DjangoTest$ django-admin startproject Test
(DjangoTest) snowfox@ubuntu:~/DjangoTest$ ls
Test  bin  lib  pyvenv.cfg

Test 프로젝트에서 testapp을 만든다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest$ cd Test
(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ python manage.py startapp testapp
(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ ls
Test  manage.py  testapp

Test/settings.py 에 app 설정 추가.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ vi Test/settings.py

...

ALLOWED_HOSTS = ['*',]		

...

INSTALLED_APPS = [
    'testapp.apps.TestappConfig',    # 이 줄 추가.

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],			# 이부분 수정

...

STATIC_ROOT = os.path.join(BASE_DIR, 'static')		# collectstatic 에 필요

Test/urls.py 에 app의 url을 포함시킨다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ vi Test/urls.py
from django.contrib import admin
from django.urls import path, include		# include 추가

urlpatterns = [
    path('', include('testapp.urls')),	# 이줄 추가
    path('admin/', admin.site.urls),
]

view를 만든다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ vi testapp/views.py

from django.shortcuts import render

# Create your views here.

from django.views.generic import TemplateView


class TestView(TemplateView):
    template_name = 'test.html'

urls.py 파일을 만든다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ vi testapp/urls.py
from django.urls import path
from .views import TestView


urlpatterns = [
    path('', TestView.as_view(), name='home'),
]

템플릿을 만든다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ mkdir templates
(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ vi templates/test.html

This is for TEST...

작성한 app이 잘 작동하는지 시험한다.

아래 명령어를 입력하고,

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ python manage.py runserver 0.0.0.0:8000

웹브라우저에서 http://192.168.0.43:8000 을 입력하여 잘 작동하면 이상 없음.

2. uWSGI 설치와 테스트
만약 설치할때 build 오류가 난다면 컴파일러가 설치되어 있는지 확인한다. 컴파일러가 없으면, 아래 명령어로 컴파일러를 설치한다.

$ sudo apt install build-essential  python3-dev 

uwsgi 를 파이썬 가상환경에서 설치한다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest$ pip install uwsgi

test.py 파일을 만들고 아래 내용을 입력한다.(manage.py 가 있는곳에)

def application(env, start_response):
    start_response('200 Ok', [('Content-Type', 'text/html')])
    return [b"uWSGI Test..."]

uwsgi가 잘 작동하는지 시험한다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ uwsgi --http :8000 --wsgi-file test.py

웹브라우저에서 접속했을때, 아래와 같은 화면이 보이면 정상이다. (여기서는 http://192.168.0.43:8000)

django application으로 시험해 본다.(manage.py가 있는 위치에서 실행)
(DjangoTest) snowfox@fox:~/DjangoTest/Test$ uwsgi –http :8000 –module Test.wsgi

3. nginx 설치

$ sudo apt install nginx
$ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2020-05-20 16:14:59 KST; 54s ago

nginx가 정상적으로 실행되는지 웹브라우저 에서 접속해 본다.

4. nginx 설정

프로젝트 디렉토리에 uwsgi_params 파일을 만들고 아래 내용을 입력하거나, /etc/nginx/uwsgi_params 파일을 복사하면 된다.

(DjangoTest) snowfox@ubuntu:~/DjangoTest/Test$ vi 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 사이트를 설정. 여기서는 따로 사이트 설정을 하지 않고, default를 이용했다.
default 파일을 아래 내용으로 바꾼다.

$ sudo vi /etc/nginx/sites-available/default

upstream django {
        server 127.0.0.1:8001;
}

server {
        listen 80;
        server_name _;

        location /media {
                alias /home/snowfox/DjangoTest/Test/media;
        }
        location /static {
                alias /home/snowfox/DjangoTest/Test/static;
        }
        location / {
                uwsgi_pass django;
                include /home/snowfox/DjangoTest/Test/uwsgi_params;
        }
}

설정을 마쳤으면 nginx를 재 실행한다.

$ sudo systemctl restart nginx

5. uwsgi 테스트

가상 환경의 django 프로젝트에서 아래 명령을 내려 시험한다. 아래 명령없이 시험하면 BadGateway 오류만 볼 뿐이다.
test.py는 위(2번)에서 작성한 파일을 사용한다.

(DjangoTest) snowfox@fox:~/DjangoTest/Test$ uwsgi --socket :8001 --wsgi-file test.py

이제, 웹브라우저에서 nginx 서버에 접속하면, test.py 에 작성한 내용을 볼 수 있다. (여기서는 http://192.168.0.43) 포트번호는 nginx에서 설정한 80번 포트다. 8001포트가 아니라.

TCP port socket 대신 unix socket 사용 – tcp port socke은 단순하지만 unix socket에 비해 overhead가 더 많다. 그래서 unix socket을 사용하는것이 더 좋다.

nginx 설정의 upstream 부분을 수정한다. 기존 설정을 주석처리하고 아래 내용으로 변경한다.

upstream django {
        # server        127.0.0.1:8001 fail_timeout=0;
        server unix:///home/snowfox/DjangoTest/Test/django.sock;
}

nginx 를 재 실행하고, uwsgi를 다시 실행한다.

# systemctl restart nginx
(DjangoTest) snowfox@fox:~/DjangoTest/Test$ uwsgi --socket django.sock --wsgi-file test.py

웹브라우저에서 접속해본다.

만약,502 Bad Gateway 오류가나며 nginx error 로그에 아래와 비슷한 오류가난다면, 소켓의 퍼미션을 조정하는 옵션을 주고 다시 시도해본다.

2020/05/21 10:43:01 [crit] 14380#14380: *1 connect() to unix:///home/snowfox/DjangoTest/django.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.0.2, server: _, request: "GET / HTTP/1.1", upstream: "uwsgi://unix:///home/snowfox/DjangoTest/django.sock:", host: "192.168.0.43"
(DjangoTest) snowfox@fox:~/DjangoTest/Test/Test$ uwsgi --socket :8001 --wsgi-file test.py --chmod-socket=666

이제, test.py 대신 작성한 Django application을 사용해 본다.

(DjangoTest) snowfox@fox:~/DjangoTest/Test$ uwsgi --socket django.sock --module Test.wsgi --chmod-socket=666

6. uWSGI 를 .ini 파일에 설정하기.

test_uwsgi.ini 파일을 아래와 같이 만든다.

(DjangoTest) snowfox@fox:~/DjangoTest/Test$ cat test_uwsgi.ini

[uwsgi]
# BASE directory
chdir   = /home/snowfox/DjangoTest/Test
# Django wsgi file
module  = Test.wsgi
# Virtualenv
home    = /home/snowfox/DjangoTest

# Proces related..
# master
master  = true
# Max. worker processes
processes       = 4
# unix socket
socket          = /home/snowfox/DjangoTest/Test/django.sock
chmod-socket    = 666
# clear env. on exit
vaccum          = true

이제, ini 파일을 이용해서 uwsgi를 실행하고 웹브라우저에서 접속해 시험해 본다.

(DjangoTest) snowfox@fox:~/DjangoTest/Test$ uwsgi --ini test_uwsgi.ini

7. 전체 시스템에 uWSGI 설치
virtualenv는 개발환경이므로, 완성된 django 프로젝트의 배포를 위해서는 전체시스템에서 uWSGI를 사용가능하도록 해야한다.

(DjangoTest) snowfox@fox:~/DjangoTest/Test$ deactivate
snowfox@fox:~/DjangoTest/Test$

uWSGI를 시스템에 설치한다. pip3 명령어가 없으면, python3-pip 패키지를 설치한다.

snowfox@fox:~/DjangoTest/Test$ sudo pip3 install uwsgi
[sudo] password for snowfox:
sudo: pip3: command not found

snowfox@fox:~/DjangoTest/Test$ sudo apt install python3-pip

snowfox@fox:~/DjangoTest/Test$ sudo pip3 install uwsgi

그리고 전체 시스템에서 (virtualenv 환경아님) 한 번 더 시험해 본다. 아래 명령어를 실행하고 웹브라우저로 웹서버 접속.

snowfox@fox:~/DjangoTest/Test$ uwsgi --ini test_uwsgi.ini

8. Emperor 모드

uWSGI 는 emperor 모드로 실행 할 수 있다. emperor 모드에서는 uWSGI 설정 파일이 있는 디렉토리를 보고 찾아낸 설정들 하나하나에대한 인스턴스(vasal 이라 부름)를 생성한다.
설정 파일이 변경될때마다, emperor가 vassal을 자동으로 재 시작한다.

vassal 설정을 위해 디렉트리를 만들고, 프로젝트에 생성한 ini 파일을 만든 디렉토리에 심볼릭 링크를 걸어둔다.

snowfox@fox:~/DjangoTest/Test$ sudo mkdir -p /etc/uwsgi/vassals
snowfox@fox:~/DjangoTest/Test$ sudo ln -s /home/snowfox/DjangoTest/Test/test_uwsgi.ini /etc/uwsgi/vassals/

위에서 설정한 unix socket 파일의 위치는 nginx에서 읽고 쓰기가 가능하도록 해줘야하지만, 이것은 바람직하지 않으므로, /tmp 에 유닉스 소켓을 만들도록 설정을 변경한다.

snowfox@fox:~/DjangoTest/Test$ cat test_uwsgi.ini
[uwsgi]
# BASE directory
chdir   = /home/snowfox/DjangoTest/Test
# Django wsgi file
module  = Test.wsgi
# Virtualenv
home    = /home/snowfox/DjangoTest

# Proces related..
# master
master  = true
# Max. worker processes
processes       = 4
# unix socket
socket          = /tmp/django.sock		# 이부분 변경.
chmod-socket    = 666
# clear env. on exit
vaccum          = true

nginx 설정도 변경하고, nginx를 재실행한다.

# vi /etc/nginx/sites-available/default

upstream django {
        # server        127.0.0.1:8001 fail_timeout=0;
        # server unix:///home/snowfox/DjangoTest/Test/django.sock;
        server unix:///tmp/django.sock;
}

이제, 아래 명령어로 시스템 전체에서 uwsgi를 실행한다.

snowfox@fox:~$ sudo uwsgi --emperor /etc/uwsgi/vassals/ --uid www-data --gid www-data

8. systemd에 서비스 등록
시스템을 재부팅하거나 웹서비스를 재시작 할 때마다 위의 명령어로 uwsgi를 실행한다는것은 매우 귀찮은 일이 될 것이다. 그러므로, uwsgi 서비스를 systemd 가 제어할 수 있도록 등록한다.
/etc/systemd/system/uwsgi.service를 만들고 아래 내용을 작성한다.

# vi /etc/systemd/system/uwsgi.service

[Unit]
Description=uWSGI Emperor
After=syslog.target

[Service]
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/vassals/ --uid www-data --gid www-data
RuntimeDirectory = uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

systemctl 명령어로 서비스를 실행하고 상태를 확인해 본다.

root@fox:/etc/systemd/system# systemctl start uwsgi
root@fox:/etc/systemd/system# systemctl status uwsgi
● uwsgi.service - uWSGI Emperor
     Loaded: loaded (/etc/systemd/system/uwsgi.service; disabled; vendor preset: enabled)
     Active: active (running) since Thu 2020-05-21 14:30:47 KST; 47s ago
   Main PID: 16108 (uwsgi)
     Status: "The Emperor is governing 1 vassals"
      Tasks: 6 (limit: 1962)
     CGroup: /system.slice/uwsgi.service
             ├─16108 /usr/local/bin/uwsgi --emperor /etc/uwsgi/vassals/ --uid www-data --gid www-data
             ├─16109 /usr/local/bin/uwsgi --ini test_uwsgi.ini
             ├─16113 /usr/local/bin/uwsgi --ini test_uwsgi.ini
             ├─16114 /usr/local/bin/uwsgi --ini test_uwsgi.ini
             ├─16115 /usr/local/bin/uwsgi --ini test_uwsgi.ini
             └─16116 /usr/local/bin/uwsgi --ini test_uwsgi.ini

May 21 14:30:51 fox uwsgi[16113]: [pid: 16113|app: 0|req: 1/1] 192.168.0.2 () {44 vars in 681 bytes} [Thu May 21 05>
May 21 14:30:51 fox uwsgi[16113]: announcing my loyalty to the Emperor...
May 21 14:30:51 fox uwsgi[16108]: Thu May 21 14:30:51 2020 - [emperor] vassal test_uwsgi.ini is now loyal
May 21 14:30:51 fox uwsgi[16114]: Not Found: /favicon.ico
May 21 14:30:51 fox uwsgi[16114]: Thu May 21 05:30:51 2020 - SIGPIPE: writing to a closed pipe/socket/fd (probably >
May 21 14:30:51 fox uwsgi[16114]: Thu May 21 05:30:51 2020 - uwsgi_response_writev_headers_and_body_do(): Broken pi>
May 21 14:30:51 fox uwsgi[16114]: OSError: write error
May 21 14:30:51 fox uwsgi[16114]: [pid: 16114|app: 0|req: 1/2] 192.168.0.2 () {42 vars in 608 bytes} [Thu May 21 05>
May 21 14:30:51 fox uwsgi[16114]: announcing my loyalty to the Emperor...
May 21 14:30:51 fox uwsgi[16108]: Thu May 21 14:30:51 2020 - [emperor] vassal test_uwsgi.ini is now loyal

이제, 새로운 django 어플리케이션을 배포하려면, 프로젝트에 uwsgi.ini 파일을 만들고 이것을 /etc/uwsgi/vassals 에 심볼릭 링크걸면 간단히 배포할 수 있게 되었다.
물론, nginx에 새로운 가상 호스트 등록도 빼먹으면 안된다!!!

참고문서: https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html

답글 남기기

Your email address will not be published.