[파이썬 웹프로그래밍] Chap 6. Django의 웹 서버 연동 원리

  • 0
  • 0
0
0

개발 환경에서 운영 환경으로 옮겨가기 위해 개발 시 지정했던 설정 사항을 몇 가지 변경해야 한다. 운영 환경의 웹 서버에서도 우리가 만든 애플리케이션을 인식할 수 잇도록 설정 사항 변경이 필요하다.

일반적인 운영 환경에서는 가상 환경을 거의 필수적으로 구성한다.

개발 서버에서 운영 서버로 소스를 복사하고 배포(Deploy)하기 위해서는 GitHub 소스 저장소나 별도의 소스 관리 서버, FTP를 사용한다.

1. 장고의 wsgi.py 파일

django-admin startproject mysite

장고의 기본 파일들과 함께 mysite/wsgi.py 파일이 만들어진다. 이 모듈이 장고와 웹 서버를 연결하는 데 필요한 파일이다.

A. mysite/wsgi.py


import os  
  
from django.core.wsgi import get_wsgi_application  
  
(...)
  
application = get_wsgi_application()

WSGI 규격에 따라 이 모듈에는 호출 가능한(callable) 애플리케이션을 정의하고 있다. 객체명은 반드시 application이어야 한다.

application 객체는 아파치와 같은 운영 웹 서버뿐만 아니라, 개발용 웹 서버인 runserver에서도 같이 사용하는 객체이다.

다른 점은 application 객체의 위치를 지정하는 방식이다.

  • 아파치나 NGINX/uWSGI(운영 웹 서버)는 설정 파일에서 지정.

    • httpd.conf 설정 파일의 WSGIScriptAlias 지시자로 지정하거나.
    • uwsgi.ini 설정 파일의 module 항목으로 지정.
  • 개발용 runserver

    • settings 모듈(mysite/settings.py)의 WSGI_APPLICATION 변수로 지정
    /mysite/settings.py
    
    (...)
    
    WSGI_APPLICATION = 'mysite.wsgi.application'
    
    (...)
    
    

import os  
  
from django.core.wsgi import get_wsgi_application  
  
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ch3.settings')  
  
(...)

웹 서버는 application 객체를 호출하여 장고의 애플리케이션을 실행하게 된다. 다만, application 객체를 호출하기 전현재의 장고 프로젝트 및 프로젝트에 포함된 모든 애플리케이션에 대한 설정 정보를 로딩하는 작업이 필요하다. settings 모듈의 위치를 /mysite/wsgi.py 파일에서 지정한다.

python manage.py runserver --settings=mysite.settings

개발용 runserver는 실행 옵션으로도 지정할 수 있다.

2. 장고의 WSGI 인터페이스

운영 환경에서는 NGINX와 같은 웹 서버 프로그램이 클라이언트 요청을 수신하므로, 요청을 수신한 웹 서버가 uWSGI와 같은 WAS를 통하여 장고 웹 애플리케이션을 호출할 수 있어야 한다.

장고는 파이썬 웹 애플리케이션을 만들어주는 프레임워크. 장고로 만든 프로그램은 was 서버에서 호출될 수 있도록 WSGI 규격을 준수해야 한다. startproject 명령을 실행하면 자동으로 생성되는 wsgi.py 파일이 이 역할을 한다.

  • uWSGI 용어

    웹 서버와 애플리케이션 중간에 위치한다고 해서, uWSGI와 같은 프로그램을 WSGI 규격에서는 미들웨어라고 부른다.

A. WSGIHandler 객체 호출

  • mysite/wsgi.py

        import os  
        from django.core.wsgi import get_wsgi_application
    
        application = get_wsgi_application()
    
    
  • ~/site-packages/django/core/wsgi.py

    
    def get_wsgi_application():  
    
        (...)
    
        return WSGIHandler()
    
    

    wsgi 모듈이 실행되는 시점에 WSGIHandler 객체가 생성되어 application 변수에 할당된다. 이 application 객체(WSGIHandler 객체)를 WAS 서버가 호출하는 것이다.

    WAS 서버 측면에서 보면. WAS 서버가 장고 애플리케이션의 wsgi.py 파일을 호출하여 WSGIHandler 객체를 얻은 다음에, 이 객체를 다시 호출하여 최종 응답을 생성하고, 웹 서버에 돌려주는 것이다.

B. WSGIHandler 클래스


class WSGIHandler(base.BaseHandler):  
    request_class = WSGIRequest  
  
    def __init__(self, *args, **kwargs):  
        super().__init__(*args, **kwargs)  
        self.load_middleware()  
  
    def __call__(self, environ, start_response):  
        set_script_prefix(get_script_name(environ))  
        signals.request_started.send(sender=self.__class__, environ=environ)  
        request = self.request_class(environ)  
        response = self.get_response(request)  
  
        response._handler_class = self.__class__  
  
        status = '%d %s' % (response.status_code, response.reason_phrase)  
        response_headers = [  
            *response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),  
        ]  
        start_response(status, response_headers)  
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):  
            # If `wsgi.file_wrapper` is used the WSGI server does not call  
 # .close on the response, but on the file wrapper. Patch it to use # response.close instead which takes care of closing all files. 
            response.file_to_stream.close = response.close 
            response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size)  
        return response

a. BaseHandler 상속

class WSGIHandler(base.BaseHandler):  

WSGI 규격에서 정의한 애플리케이션의 역할을 수행하는 클래스.

부모 클래스인 BaseHandler에는 장고의 settings.py 파일에 등록된 MIDDLEWARE에 대한 처리 기능도 포함되어 있다.

b. WSGIHandler 클래스의 __call__ 메소드

    def __call__(self, environ, start_response):  
        set_script_prefix(get_script_name(environ))  
        signals.request_started.send(sender=self.__class__, environ=environ)  
        request = self.request_class(environ)  
        response = self.get_response(request)  
  
        response._handler_class = self.__class__  
  
        status = '%d %s' % (response.status_code, response.reason_phrase)  
        response_headers = [  
            *response.items(),
            *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),  
        ]  
        start_response(status, response_headers)  
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):  
            # If `wsgi.file_wrapper` is used the WSGI server does not call  
 # .close on the response, but on the file wrapper. Patch it to use # response.close instead which takes care of closing all files. 
            response.file_to_stream.close = response.close 
            response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size)  
        return response

  • Line 1.
    • WSGI 서버에서 WSGIHandler 객체를 호출할 때(application(environ, start_response)), __call__() 메소드가 실행된다.
  • Line 2~16.
    • __call__() 메소드가 WSGI 규격의 애플리케이션 스펙을 구현한 내용이다.

장고는 웹 애플리케이션을 만들어주는 프레임워크이고 WSGI 규격의 애플리케이션 스펙을 구현하기 위해 wsgi.py 파일을 제공한다. WSGI 서버에서는 application 호출자(callable)를 호출하는데, wsgi.py 파일에서 호출자를 정의한다. 장고에서는 WSGIHandler 클래스로 정의하고 있다.

웹 서버 또는 WAS 서버가 장고 애플리케이션을 실행하기 위해 application 호출자가 정의된 wsgi.py 파일의 위치를 알아야 한다. 웹/WAS 서버(Apache, NGINX, uWSGI)의 설정 파일에는 application 호출자의 경로가 정의되어야 한다.

  • settings.py

    
    (...)
    
    WSGI_APPLICATION = 'mysite.wsgi.application'
    
    (...)
    
    

    개발용 웹 서버인 runserver도 settings.py 파일에 정의된 항목으로 호출자의 경로를 파악하여, WSGI 규격에 따라 application 호출자를 호출한다.

3. 운영 서버 적용 전 장고의 설정 변경 사항

운영 서버에 적용하기 전, 보안, 성능 등을 고려하여 운영 환경에 맞는 설정으로 변경해야 한다.

A. 운영 서버에 배포한 이후 설정 파일 확인하기

python manage.py check --deploy

B. SECRET_KEY

SECRET_KEY는 암호화가 필요할 때 사용되는 항목으로 외부에 노출되어서는 안된다. 따라서 운영 모드에서는 환경 변수나 파일에 저장한 후, settings.py 파일에서 읽어 들이는 것이 좋다.

데이터베이스의 접속 패스워드도 개발 모드에서는 settiings.py 파일에 하드코딩되어 있기 때문에 다른 곳에 저장하는 것이 좋다.

a. settings.py

import os

SECRET_KEY = os.environ['SECRET_KEY']

# OR

with open(os.path.join(BASE_DIR, 'www_dir', 'secret_key.txt')) as f:
    SECRET_KEY = f.read().strip()

C. 디버그 정보

DEBUG 설정값을 False로 셋팅하여 디버그 정보가 웹 브라우저에 노출되지 않도록 한다.

DEBUG = False이면, ALLOWED_HOSTS 항목을 설정해야 한다. 공격자가 HTTP Host 헤더를 변조하여 CSRF 공격을 할 수 있기 때문. 장고가 실행되는 서버의 IP 주소나 도메인명을 등록한다.

a. settings.py

(...)

DEBUG = False

ALLOWED_HOSTS = [ '192.168.56.101' ]

(...)

D. 정적 파일 위치.

운영 모드에서는 웹 서버에게 정적 파일들의 위치를 알려줘야 한다. settings 모듈의 STATIC_ROOT 항목은 장고의 collectstatic 명령 실행 시 정적 파일들을 한곳에 모아주는 디렉토리이다.

디렉토리는 웹 서버의 설정 파일에도 등록해줘야 한다.

a. settings.py

STATIC_ROOT = os.path.join(BASE_DIR, 'www_dir', 'static')

b. collectstatic 명령

python manage.py collectstatic

  • 주의
    • settings 모듈의 STATICFILES_DIRS 항목STATIC_ROOT 항목에서 정의된 디렉토리가 포함되면 안된다.
      • STATICFILES_DIRS 항목에 정의된 디렉토리에서 정적 파일을 찾아 STATIC_ROOT 디렉토리에 복사해주기 때문이다.

E. 파일 액세스

운영 모드에서는 웹 서버 프로세스의 소유자 권한으로 해당 파일들을 액세스할 수 있어야 한다. settings 모듈의 DATABASES 항목에서 NAME 속성값의 경로를 db/db.sqlite3로 변경하고, 해당 디렉토리 및 파일의 액세스 권한을 변경해줘야 한다.

SQLite 데이터베이스 파일의 위치를 옮기고, SQLite 데이터베이스가 있는 디렉토리 및 파일에 apache 사용자가 접근/읽기/쓰기 가능하도록 설정한 것이다.


(...)

DATABASES = {  
    'default': {  
        'ENGINE': 'django.db.backends.sqlite3',  
        'NAME': os.path.join(BASE_DIR, 'db', 'db.sqlite3'), 
 }  
}

(...)

mkdir db mv db.sqlite3 db/ chmod 777 db/ cbmod 666 db/db.sqlite3

chmod 777 logs. chmod 666 logs/mysite.log

F. 캐시 서버와 데이터베이스 서버 액세스 허용

웹 환경에서는 캐시 서버와 데이터베이스 서버가 사용되는 경우가 많다. 이들 서버는 웹 서버보다는 보안 기능이 취약하므로 외부에서 직접 액세스하는 것은 바람직하지 않다. 그래서 캐시 서버와 데이터베이스 서버연결하는 것은 웹 서버 또는 WAS 서버로 제한하는 게 좋다.

G. 메일 발송 기능

장고는 발신자 주소를 디폴트로 root@localhost (에러 발생 시 발신자) 및 webmaster@localhost (그 외 사항에 대한 메일 발신자)로 지정한다. 메일 서버에 따라서 보안상의 이유로 허용하지 않기도 해서 SERVER_EMAILDEFAULT_FROM_EMAIL 설정 항목을 사용해서 발신자 주소를 변경하는 게 좋다.

참고문헌

김석훈, "Django로 배우는 쉽고 빠른 웹개발 파이썬 웹프로그래밍", 개정판, 4쇄, 한빛미디어, 2020년

#Django #웹서버 #wsgi #설정 #운영서버

0분전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.