Two Scoops of Django 3.x를 읽고 정리한 글입니다.
최선의 장고 설정 방법은?
운영에 필요 없는 설정이나 SECREY_KEY와 같은 보안 관련 설정은 저장소에서 빼야하기 때문에 보통 local_settings 안티 패턴이 활용되었다. 하지만 다음과 같은 문제로 인해 권장하지 않게 되었다.
한 개의 settings.py
파일을 이용하기보다는 settings/
디렉터리 아래에 여러 개의 셋업 파일을 구성한다.
# Settings Directory
settings/
├── __init__.py
├── base.py # 프로젝트의 모든 인스턴스에 적용되는 공용 세팅 파일
├── local.py # 로컬 환경에서 작업할 때 쓰이는 파일. 디버그모드, 로그 레벨, 디버그 도구 활성화 등이 설정되어있다. dev.py로 쓰기도 한다.
├── staging.py # 스테이징 서버는 운영 서버에서 구동되는 세미 프라이빗 버전이다. 운영 환경으로 코드가 이동되기 전에 관리자 및 고객들의 확인을 위한 시스템이다.
├── test.py # 테스트 실행기, 인메모리 데이터베이스 정의, 로그 세팅 등을 포함한 테스트를 위한 세팅
├── production.py # 운영 서버에서 실제로 운영되는 세팅 파일. prod.py로 쓰기도 한다.
여러개의 설정 파일 중 settings/local.py
설정 파일을 이용해 장고/파이썬 쉘과 장고 서버를 구동해보자.
python manage.py shell --settings=twoscoops.settings.local # run python/django shell
python manage.py runserver --settings=twoscoops.settings.local # run server
추가로, --settings
옵션 뿐만 아니라 DJANGO_SETTINGS_MODULE
과 PYTHONPATH
환경 변수를 조건에 맞는 세팅 모듈 패스로 설정하는 방법이 있다.
환경 | --settings(또는 DJANGO_SETTINGS_MODULE) 옵션값 |
---|---|
로컬 개발 환경 | twoscoops.settings.local |
스테이징 서버 | twoscoops.settings.staging |
테스트 서버 | twoscoops.settings.test |
운영 서버 | twoscoops.settings.production |
표 5-1. 서버에 따른 DJANGO_SETTINGS_MODULE 세팅
개발 환경에서만 적용이 필요한 설정을 작성한다.
# settings/local.py
from .base import *
DEBUG = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'twoscoops',
'HOST': 'localhost',
}
}
INSTALLED_APPS += ['debug_toolbar', ]
개발자 본인만의 환경이 필요한 경우는 어떻게 해야할까? 이 경우 local.py(dev.py)세팅 파일로는 부족할 수도 있다.
그럼에도 localsettings.py를 따로 제작하는것이 아닌, local{{name}}.py와 같은 파일을 생성해서 버전 컨트롤에서 관리하는것이 맞다.
# settings/local_pydanny.py
from .local import *
# Set short cache timeout
CACHE_TIMEOUT = 30
이유는 모든 개발 환경에 대한 버전관리가 가능하다는 점과 팀원 간 서로의 개발 세팅을 참고할 수 있다는 점 때문이다. 이는 서로의 개발 세팅에 문제를 제기할 수도 있다는 장점이 있다.
# comtom settings
settings/
__init__.py
base.py
local_audreyr.py
local_pydanny.py
local.py
staging.py
test.py
production.py
비밀 키(SECRET_KEY, AWS키, API키 등)들은 설정값 들이지 코드가 아니다.
비밀키는 배포 환경에 따라 다르지만 코드는 그렇지 않다. 분리되어야 하는 가장 큰 이유다. 또한 비밀 키들은 보안 차원에서도 버전 관리 시스템을 통해 관리되어서는 안된다.
본서에서는 이를 해결하기 위한 방법으로 환경 변수 패턴(environment variables pattern)을 활용할 것을 권고한다.
환경 변수 패턴의 장점
settings/local.py
를 나눠 쓸 수 있다.# Linux/OSX에서 환경 변수 설정하기
$ export SOME_SECRET_KEY=1c3-cr3am-15-yummy
$ export AUDREY_FREEZER_KEY=y34h-r1ght-d0nt-t0uch-my-1c3-cr34m
서버를 구축하는 방법은 크게 두가지로 나뉜다.
1번의 경우에는 로컬 환경에서 설정했던 것과 같이 환경변수를 수작업으로 설정하는 방법이 있을 것이다. 반면 2번은 배포 도구의 문서를 봐야할 수 있다.
Pass 플랫폼은 Heroku, Python Anywhere, Platform.sh등이 있으며 이 도구들의 지침서를 확인해야 한다.
환경변수가 잘 설정되었다면 프로젝트에서 아래와 같이 환경변수에 접근한다.
# Python's REPL에서 접근
>>> import os
>>> os.environ['SOME_SECRET_KEY'] '1c3-cr3am-15-yummy'
# Python 코드에서 접근
# Top of settings/production.py
import os
SOME_SECRET_KEY = os.environ['SOME_SECRET_KEY']
디버깅을 위해서는 비밀 키를 가져오지 못했을 때 단순 KeyError가 아닌 명확한 오류 메시지가 필요하다. 환경 변수가 존재하지 않을 때 원인을 좀 더 쉽게 알려주는 코드가 있다.
# settings/base.py
import os
# 일반적으로 장고로부터 직접 무언가를 설정 파일로 임포트해 올 일은 없을 것이며 해서도 안된다.
# (예기치 않는 부작용을 일으킬 수 있다)
# 단 ImproperConfigured는 예외이다.
from django.core.exceptions import ImproperlyConfigured
def get_env_variable(var_name):
"""Get the environment variable or return exception."""
try:
return os.environ[var_name]
except KeyError:
error_msg = 'Set the {} environmentvariable'.format(var_name)
raise ImproperlyConfigured(error_msg)
이제 아래와 가팅 환경 변수에서 비밀 키를 가져오면 된다.
# get_env_variable함수를 이용해 환경 변수를 가져오는 코드
SOME_SECRET_KEY = get_env_variable('SOME_SECRET_KEY')
# 환경 변수를 가져오지 못했을 때의 에러 메시지
django.core.exceptions.ImproperlyConfigured: Set the SOME_SECRET_KEY
environment variable.
아파치, Nginx등과 같이 환경 변수를 이용할 수 없을 때가 있다. (별도의 환경 변수 시스템이 존재)
이 때 local_settings안티 패턴이 아니라 비밀 파일 패턴(secrets file pattern)을 활용한다.
비밀 파일 패턴 구현 방법은 다음과 같다.
// secrets.json
{
"FILENAME": "secrets.json",
"SECRET_KEY": "I've got a secret!",
"DATABASES_HOST": "127.0.0.1",
"PORT": "5432"
}
# secrets.json을 활용하기 위해 기본 베이스 settings 모듈에 추가
# settings/base.py
import json
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
from django.core.exceptions import ImproperlyConfigured
# JSON-based secrets module
with open('secrets.json') as f:
secrets = json.load(f)
def get_secret(setting, secrets=secrets):
'''Get the secret variable or return explicit exception.'''
try:
return secrets[setting]
except KeyError:
error_msg = 'Set the {0} environment variable'.format(setting)
raise ImproperlyConfigured(error_msg)
SECRET_KEY = get_secret('SECRET_KEY')
.gitignore
에 추가한다.각 세팅 파일에 해당하는 requirements파일을 사용할 것을 추천한다. 각 서버에서 그 환경에 필요한 컴포넌트만 설치하자는 의미다.
# 세분화 된 requirements
requirements/
├── base.txt
├── local.txt
├── staging.txt
├── production.txt
Django==3.2.0
psycopg2-binary==2.8.8
djangorestframework==3.11.0
-r base.txt # base.txt requirements 파일 포함
coverage==5.1
django-debug-toolbar==2.2
-r base.txt # base.txt requirements 파일 포함
coverage==5.1
-r base.txt # base.txt requirements 파일 포함
환경별 requirements의 작성을 완료하였다. 특정 requirementst.txt를 선택해서 설치하는 방법은 다음과 같다.
$ pip install -r requirements/{{file_name}}.txt
다음과 같이 하드코딩된 파일 경로는 피한다.
# settings/base.py
# Configuring MEDIA_ROOT
# 절대 따라하지 말라! 단 한 명의 설정에 맞추어 하드코딩 되었다.
MEDIA_ROOT = '/Users/pydanny/twoscoops_project/media'
# Configuring STATIC_ROOT
# 절대 따라하지 말라! 단 한 명의 설정에 맞추어 하드코딩 되었다.
STATIC_ROOT = '/Users/pydanny/twoscoops_project/collected_static'
# Configuring STATICFILES_DIRS
# 절대 따라하지 말라! 단 한 명의 설정에 맞추어 하드코딩 되었다.
STATICFILES_DIRS = ['/Users/pydanny/twoscoops_project/static']
# Configuring TEMPLATES
# 절대 따라하지 말라! 단 한 명의 설정에 맞추어 하드코딩 되었다.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
DIRS: ['/Users/pydanny/twoscoops_project/templates',]
},
]
경로를 하드 코딩하지 않도록 한다. 위의 코드는 danny의 컴퓨터가 아니면 반드시 문제에 봉착할 것이다.
경로 문제를 해결하기 위해 settings모듈 최상부에 BASE_DIR이란 root변수가 존재한다. BASE_DIR은 base.py의 위치에 따라 결정되기 때문에 어디서든 프로젝트를 구동할 수 있게 된다.
python 3.4 이후의 pathlib를 활용하면 BASE_DIR과 같은 경로 세팅을 깔끔하게 할 수 있다.
# Pathlib 사용하여 루트를 검색
# settings/base.py의 윗부분
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
MEDIA_ROOT = BASE_DIR / 'media'
STATIC_ROOT = BASE_DIR / 'static_root'
STATICFILES_DIRS = [BASE_DIR / 'static']
TEMPLATES = [
{
'BACKEND': django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
},
]
추가로 여러분의 장고 세팅이 기본 설정과 얼마나 다른지 알고 싶다면 장고 관리 콘솔에서 diffsettings
를 실행해보자.