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