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

