Python WAS 구축하기 ( Django, Nginx, Gunicorn )
Python으로 REST API 서비스를 위한 WAS(Web Application Server) 구축을 진행합니다.
Django만으로도 REST API를 오픈할 수 있지만
Django의 runserver는 단순히 테스트만을 위한 기능으로
운영환경에서 사용하면 성능상 문제를 겪게 됩니다.
운영모드에서 Gunicorn 같은 WSGI(Web Server Gateway Interface) 미들웨어와 연동이 필요합니다.
위와 같은 구조에서 Private Physical Server
위에 놓여진 것들을 구성하는 것이 이번 글의 목표입니다.
즉, Linux 환경에서 Nginx, Gunicorn, Django를 연동하는 과정을 보여드리겠습니다.
Python, virtualenv, Django, REST API 에 대해서는 설명없이 설치, 설정 방법을 기술할 예정이며
WSGI에 대해서 간략히 설명 후 Nginx, Gunicorn 설치, 설정을 설명하겠습니다.
Django와 REST API 에 대한 설명은 다른 글에서 진행하도록 하겠습니다.
그리고 Celery에 대해서도 글을 작성할 예정입니다.
환경 구성
작업환경
- CentOS 7
구성내역
- Python 3.6
- Nginx
- Gunicorn
- Django
필요 프로그램 설치
$ yum install gcc gcc-c++
$ yum install zlib-devel
$ yum install openssl openssl-devel
$ yum install sqlite sqlite-devel
$ yum install wget tree
위의 프로그램들이 설치되지 않은채로 Python이 컴파일 됐다면 나중에 문제가 생길 수 있습니다.
예를들어 sqlite-devel이 설치되어있지 않다면 sqlite를 사용하는 시점에 라이브러리를 찾을 수 없다는 에러가 발생합니다.
필요한 프로그램들을 설치 후 Python을 재컴파일 하셔야합니다.
wget, tree은 필수적으로 설치해야하는 프로그램은 아닙니다. 다만 설치하는 과정, 테스트 하는 과정 중에 사용되서 함께 넣었습니다.
User, Group 생성
$ groupadd g_news
$ useradd -g g_news u_news
$ passwd u_news
Python 환경구성
Python 3.6.5rc1 컴파일 설치
브라우저로 https://www.python.org/ftp/python
에 접속해보면 Python 소스가 버전별로 정리되어있습니다. 필요한 버전을 설치하시면 됩니다.
$ mkdir /tmp/python-install -p
$ cd /tmp/python-install
$ wget https://www.python.org/ftp/python/3.6.6/Python-3.6.6rc1.tgz
$ tar -zxvf Python-3.6.6rc1.tgz
$ cd Python-3.6.6rc1
$ ./configure --enable-optimizations
$ make altinstall
Symlinks 설정
$ cd /usr/local/bin
$ ln -s python3.6 python3
python3 파일이 이미 있다면 /usr/local/bin/python3
을 삭제 후 다시 symlinks 설정해주면 됩니다.
확인작업
$ python3 --version
Python 3.6.6rc1
불필요 파일 제거
$ cd /tmp
$ rm -rf ./python-install
virtualenv
virtualenv 만들기
$ mkdir /engn001
$ cd /engn001
$ python3 -m venv news_venv
$ chown -R u_news:g_news news_venv
virtualenv 사용하기
$ su - u_news
u_news$ cd /engn001/news_venv
u_news$ . bin/activate
(news_venv) u_news$
PIP를 이용한 패키지 설치
PIP를 이용해서 필요한 패키지를 설치할 수 있습니다.
지금은 DJango와 DJango-Rest-Framework을 설치하겠습니다.
DJango-REST-Framework 설치
(news_venv) u_news$ pip install django djangorestframework
그 외 필요한 패키지 설치
(news_venv) u_news$ pip install serializers
serializers
라는 패키지를 추가 설치했습니다.
serializers
는 데이터를 JSON, XML과 같은 native 데이터로 바꿔주는 역할을 합니다.
이 외에도 필요한 Python 패키지가 있을 경우에는 pip 명령어로 추가 설치하시면 됩니다.
Framework : DJango
프로젝트 생성
(news_venv) u_news$ cd /engn001/news_venv
(news_venv) u_news$ mkdir /engn001/news_venv/run
(news_venv) u_news$ django-admin.py startproject news
(news_venv) u_news$ cd /engn001/news_venv/news
(news_venv) u_news$ python manage.py startapp article
생성된 프로젝트의 구조를 확인해보겠습니다.
(news_venv) u_news$ tree /engn001/news_venv/news
.
|-- article
| |-- __init__.py
| |-- admin.py
| |-- apps.py
| |-- migrations
| | `-- __init__.py
| |-- models.py
| |-- tests.py
| `-- views.py
|-- manage.py
`-- news
|-- __init__.py
|-- __pycache__
| |-- __init__.cpython-36.pyc
| `-- settings.cpython-36.pyc
|-- settings.py
|-- urls.py
`-- wsgi.py
Settings.py 수정
(news_venv) u_news$ cd /engn001/news_venv/news/news
(news_venv) u_news$ vi settings.py
INSTALLED_APP 리스트에 rest_framework
은 필수로 등록하고 아까 생성한 app의 이름을 등록해줍니다.
INSTALLED_APPS = [
...
'rest_framework',
'article'
]
문서 하단에 아래 내용을 추가합니다.
Admin 혹은 인증된 대상만 REST API를 사용하게 하려면 아래 주석을 제거하시면 됩니다.
# REST_FRAMEWORK
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.IsAdminUser',
# 'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10
}
Sample Rest API 구성
샘플로 Article, Author, Reply 라는 객체와 REST API를 구성하겠습니다.
이번 글에서는 설명없이 진행하고 소스코드에 대한 내용은 다른 글에서 설명하겠습니다.
apps.py
/engn001/news_venv/news/article/apps.py
를 수정합니다.
from django.apps import AppConfig
class ArticleConfig(AppConfig):
name = 'article'
models.py
/engn001/news_venv/news/article/models.py
를 수정합니다.
from django.db import models
class Author(models.Model):
nick_name = models.CharField(max_length=30)
mail = models.CharField(max_length=30)
def __str__(self):
"""A string representation of the model."""
return self.nick_name
class Article(models.Model):
title = models.CharField(max_length=40)
content = models.CharField(max_length=1000)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
"""A string representation of the model."""
return self.title
class Reply(models.Model):
article = models.ForeignKey(Article, related_name='replies', on_delete=models.CASCADE)
content = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
"""A string representation of the model."""
return self.content
serializers.py
/engn001/news_venv/news/article/serializers.py
를 수정합니다.
from article.models import Article
from article.models import Author
from article.models import Reply
from rest_framework import serializers
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('id', 'title', 'content', 'author', 'replies')
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'nick_name', 'mail')
class ReplySerializer(serializers.ModelSerializer):
class Meta:
model = Reply
fields = ('id', 'article', 'content', 'author')
views.py
/engn001/news_venv/news/article/views.py
를 수정합니다.
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from .serializers import ArticleSerializer
from .serializers import AuthorSerializer
from .serializers import ReplySerializer
from .models import Article
from .models import Author
from .models import Reply
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
#permission_classes = [IsAuthenticated, IsAdmin, IsAccountAdminOrReadOnly]
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
#permission_classes = [IsAuthenticated, IsAdmin, IsAccountAdminOrReadOnly]
class ReplyViewSet(viewsets.ModelViewSet):
queryset = Reply.objects.all()
serializer_class = ReplySerializer
#permission_classes = [IsAuthenticated, IsAdmin, IsAccountAdminOrReadOnly]
urls.py
/engn001/news_venv/news/news/urls.py
를 수정합니다.
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from rest_framework import routers
from article import views
router = routers.DefaultRouter()
router.register(r'articles', views.ArticleViewSet)
router.register(r'authors', views.AuthorViewSet)
router.register(r'replies', views.ReplyViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
DB Migration
(news_venv) u_news$ cd /engn001/news_venv/news
(news_venv) u_news$ python manage.py makemigrations
(news_venv) u_news$ python manage.py migrate
Static 데이터 모으기
static/
디렉토리를 STATIC_ROOT
파라미터로 설정 후
Django가 정적 데이터를 모으도록 명령어를 실행시켜 줍니다.
(news_venv) u_news$ cd /engn001/news_venv/news
(news_venv) u_news$ echo "STATIC_ROOT = os.path.join(BASE_DIR, 'static/')" >> news/settings.py
(news_venv) u_news$ python manage.py collectstatic
WSGI를 통해서 서비스를 하게 될 때는 필수적으로 진행해주셔야 합니다.
WSGI를 통하지 않고 Django runserver를 이용해서 테스트 하는 단계라면 생략하셔도 진행가능합니다.
확인작업
(news_venv) u_news$ tree /engn001/news_venv/news/static
Runserver
DJango의 runserver 기능을 이용해서 서버를 띄웁니다.
다만, DJango runserver는 단순히 테스트를 위한 기능입니다.
운영환경에서는 미들웨어와 연동해서 서버를 띄워야합니다.
(news_venv) u_news$ cd /engn001/news_venv/news
(news_venv) u_news$ python manage.py runserver 0.0.0.0:8000
http://localhost:8000
에 접속해보면 서버가 띄워진 것을 확인할 수 있습니다.
$ curl -i http://localhost:8000
브라우저로 접속하면 REST API 테스트를 해볼 수 있는 페이지가 출력됩니다.
GET, POST, PUT, DELETE 모두 테스트 가능합니다.
curl -X POST -H "Content-Type: application/json" -d '{"nick_name":"dveamer", "mail":"dveamer@gmail.com"}' http://localhost:8000/authors/
curl -i http://localhost:8000/authors/1/
References
- REST API https://dev.to/enether/managing-restful-urls-in-django-rest-framework
- http://www.django-rest-framework.org/api-guide/viewsets/
WSGI (Web Server Gateway Interface) 란?
Web서버가 받은 호출을 Python 어플리케이션에게 전달하고 응답받기 위한 호출조약(Calling Convention) 입니다.
WSGI 미들웨어란?
WSGI 호출조약의 구현체라고 볼 수 있습니다.
Private Physical Server
위의 녹색 선으로 그려진 모든 것들을 합쳐서 WAS(Web Application Server) 라고 부릅니다.
Application을 담을 Web Server 라고 생각하면 이해가 쉽습니다.
그리고 파란색으로 칠해진 WSGI module과 WSGI Process를 합쳐서 WSGI middleware라고 부릅니다.
하나로 구성된 것이 아니라 일부는 Nginx에 내장되어있고 일부는 프로세스로 띄워진 형태입니다.
WSGI middleware는 Web 서버와 application을 연결시켜줍니다.
WSGI module과 WSGI Process는 WSGI 전용 프로토콜로 정보를 주고 받습니다.
CGI가 무엇인지 아시는 분들은 이해가 쉬울 수 있습니다.
CGI와 비슷한데 WSGI는 Python전용이며
CGI는 프레임워크 종류, 웹서버의 종류에 의존적이지만
WSGI는 프레임워크와 웹서버 선택이 자유롭습니다.
WSGI Middleware Component 기능
- 호출된 URL에 대한 라우팅 기능
- 하나의 프로세스에서 다중 어플리케이션 동작하도록 처리
- 로드밸런싱
- 컨텐츠 전처리 ( 예 : XSLT stylesheets )
WSGI Middleware 종류
- Bjoern
- uWSGI
- mod_wsgi
- CherryPy
- Gunicorn
WAS 구성
Nginx, Gunicorn을 이용해서 진행합니다.
gunicorn 설치가이드
설치
(news_venv) u_news$ pip install gunicorn
설정파일 작성
/engn001/news_venv/news/gunicorn_cfg.py
파일을 생성하고 아래와 같이 설정정보를 입력합니다.
# vi /engn001/news_venv/news/gunicorn_cfg.py
daemon=True
bind='unix:/engn001/news_venv/run/gunicorn.sock news.wsgi:application'
workers=5
기동
(news_venv) u_news$ cd /engn001/news_venv/news
(news_venv) u_news$ gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
확인작업
(news_venv) u_news$ ps -ef | grep news
u_news 212 1 0 09:26 ? 00:00:00 /engn001/news_venv/bin/python3 /engn001/news_venv/bin/gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
u_news 215 212 3 09:26 ? 00:00:00 /engn001/news_venv/bin/python3 /engn001/news_venv/bin/gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
u_news 216 212 4 09:26 ? 00:00:00 /engn001/news_venv/bin/python3 /engn001/news_venv/bin/gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
u_news 219 212 4 09:26 ? 00:00:00 /engn001/news_venv/bin/python3 /engn001/news_venv/bin/gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
u_news 220 212 4 09:26 ? 00:00:00 /engn001/news_venv/bin/python3 /engn001/news_venv/bin/gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
u_news 222 212 3 09:26 ? 00:00:00 /engn001/news_venv/bin/python3 /engn001/news_venv/bin/gunicorn -c /engn001/news_venv/news/gunicorn_cfg.py news.wsgi:application
gunicorn 프로세스가 총 6개 구동 중인 것을 확인할 수 있습니다.
main 프로세스 하나와 5개의 worker 프로세스 개수입니다. worker 프로세스 개수는 설정파일의 worker 파라미터로 입력한 값 입니다.
Nginx 설치가이드
(news_venv) u_news$ deactviate
u_news$ exit
$ yum install nginx
만약에 nginx 패키지 조회불가로 에러가 난다면 아래와 같이 진행하시면 됩니다.
epel(Extra Packages for Enterprise Linux) yum 저장소를 추가하는 내용입니다.
$ yum install epel-release
$ yum install nginx
기동 확인
Nginx의 환영페이지를 출력해봅니다.
$ curl -i http://localhost
아직은 Nginx와 gunicorn이 연동이 안됐지만 결과를 비교해보기 위해서
사용자 리스트 URI를 호출해봅니다. 404 Not Found 에러페이지가 출력될 것입니다.
$ curl -i http://localhost/authors/
Nginx-gunicorn 연동가이드
Nginx 기동중지
$ nginx -s stop
Nginx 기본설정 수정
/etc/nginx/nginx.conf
파일을 백업해둡니다.
$ cd /etc/nginx
$ cp ngnix.conf nginx.conf_origin
/etc/nginx/nginx.conf
파일의 server 정보를 제거합니다.
server 정보는 38라인부터 57라인에 해당합니다. 백업해놨으니 지우시면 됩니다.
# vi /etc/nginx/nginx.conf
# 삭제 혹은 주석
# server {
# ....
# }
Nginx server 등록
/etc/nginx/conf.d/news.conf
파일을 생성하고 아래 내용을 입력합니다.
# vi /etc/nginx/conf.d/news.conf
server {
listen 80;
server_name ${YOUR_SERVER_NAME};
root /usr/share/nginx/html;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root ${YOUR_PROJECT_DIR};
}
location / {
include proxy_params;
proxy_pass http://unix:${YOUR_RUN_DIR}/run/gunicorn.sock;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
입력시 상황에 맞게 수정이 필요한 부분이 3군데 있습니다.
-
YOUR_SERVER_NAME : 도메인주소 혹은 IP를 입력해주시면 됩니다. Nginx의 rewrite, foward 기능 사용시 주로 사용됩니다. 내부연계용 REST API 구성시에는 의미있게 쓰일 경우가 없으니
127.0.0.1
과 같은 값으로 입력하셔도 무방합니다. -
YOUAR_PROJECT_DIR : 프로젝트가 위치한 절대주소를 적어주시면 됩니다. 현재 진행 중인 예제의 프로젝트 절대주소는
/engn001/news_venv/news
입니다. -
YOUAR_RUN_DIR : Gunicorn이 bind된 run 디렉토리의 절대주소를 적어주시면 됩니다. 현재 진행 중인 예제의 프로젝트 절대주소는
/engn001/news_venv/run
입니다.
Nginx 기동
$ nginx
기동 확인
$ curl -i http://localhost/authors/
정상적으로 아까 등록해둔 authors json이 출력됩니다.
References
결론
이로써 Nginx + Gunicorn 위의 Django REST API application이 올라간 WAS 구성을 완료했습니다.
아래의 사항들은 추후에 추가적으로 글을 작성하거나 현재 글을 수정하도록 하겠습니다.
- RabbitMQ와 Celery 구성을 통한 비동기 처리
- 외부 DB 연동
- REST API 구성 소스코드 설명
Associated Posts
관련된 주제를 살펴볼 수 있도록 동일한 Tag를 가진 글들을 모아뒀습니다. 제목을 눌러주세요.-
Git CLI 개인환경 설정
( 이미지 출처 : https://git-scm.com )git-cli 세팅 후 필요한 개인적인 환경설정 작업들을 기술합니다.
... 더 읽기 -
Windows WSL2를 이용한 Ubuntu, Docker, Kubernetes 환경구성
( 이미지 출처 : https://docs.microsoft.com )Windows 환경에서 Ubuntu를 사용하기 위한 WSL 세팅 후 필요한 개인적인 환경설정 작업들을 기술 합니다.
... 더 읽기 -
Setting Android Studio
( 이미지 출처 : https://developer.android.com/ )이 글은 Ubuntu 환경에서 Android Studio를 설치하고 설정하는 방법을 설명합니다.
특히 기존에 Google Play에 등록해둔 앱을 새로운 Ubuntu & Android Studio 환경에서 개발하기 위해 개발환경을 구축하는 분들을 위한 글입니다.
Android 앱 개발에 대한 내용은 다루지 않습니다.... 더 읽기 -
Ubuntu 18.04 LTS
-
Zappa를 이용해 AWS Lambda에 Flask 올리기
-
Python3.6 설치
-
Python SimpleHTTPServer
-
Docker 설치 후 이미지 보관 디렉토리 변경
-
Resizing Images On AWS Lambda
-
Deploying Python Zip To AWS Lambda
-
HTTPD (Apache HTTP Server) 설치하기
-
Linux에서 컴파일 설치법을 알아야하는 이유
-
Docker를 이용해서 SonarQube 간단히 설정하기
-
SonarQube 로 Android 소스코드 분석하기
-
How To Set Up SVN(Subversion) Repositories
-
Install CentOS with Docker
-
Nginx SSL 적용방법
-
How to install Nginx & Tomcat
-
Ubuntu 설치 후 초기세팅
-
Python 학습내용 기록
-
How to install MariaDB 10.01 on Ubuntu 14.04 LTS
-
How to install Java 8 On Ubuntu
-
Ubuntu 14.04 LTS