[Two Scoops of Django] 5장. settings와 requirements 파일

guava·2021년 9월 9일
1

Two Scoops of Django

목록 보기
5/12
post-thumbnail

Two Scoops of Django 3.x를 읽고 정리한 글입니다.

최선의 장고 설정 방법은?

  1. 버전 컨트롤 시스템으로 모든 설정 파일을 관리한다.
  2. 반복 되는 설정을 없앤다.
  3. 암호나 비밀 키 등은 안전하게 보관한다.

5.1. 버전 관리 되지 않는 로컬 세팅은 피한다

운영에 필요 없는 설정이나 SECREY_KEY와 같은 보안 관련 설정은 저장소에서 빼야하기 때문에 보통 local_settings 안티 패턴이 활용되었다. 하지만 다음과 같은 문제로 인해 권장하지 않게 되었다.

  • 버전 컨트롤에 기록되지 않은 코드가 local_settings를 사용하는 여러 머신에 존재하게 된다.
  • 운영 환경에서만 발생하는 문제점이 발생할 수 있다.
  • 로컬 환경의 버그를 찾아서 push했는데 local_settings.py로부터 기인했을 경우가 있다. (쓸데없는 커밋)
  • 여러 팀원이 서로의 local_settings.py를 복사해서 쓴다면, DRY원칙(Don't Repeat Yourself)에 위배되는 것이기도 하다.

5.2. 여러개의 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_MODULEPYTHONPATH 환경 변수를 조건에 맞는 세팅 모듈 패스로 설정하는 방법이 있다.

환경--settings(또는 DJANGO_SETTINGS_MODULE) 옵션값
로컬 개발 환경twoscoops.settings.local
스테이징 서버twoscoops.settings.staging
테스트 서버twoscoops.settings.test
운영 서버twoscoops.settings.production

표 5-1. 서버에 따른 DJANGO_SETTINGS_MODULE 세팅

개발 환경의 settings 파일 예제

개발 환경에서만 적용이 필요한 설정을 작성한다.

# 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

5.3. 코드에서 설정 분리하기

비밀 키(SECRET_KEY, AWS키, API키 등)들은 설정값 들이지 코드가 아니다.

비밀키는 배포 환경에 따라 다르지만 코드는 그렇지 않다. 분리되어야 하는 가장 큰 이유다. 또한 비밀 키들은 보안 차원에서도 버전 관리 시스템을 통해 관리되어서는 안된다.

본서에서는 이를 해결하기 위한 방법으로 환경 변수 패턴(environment variables pattern)을 활용할 것을 권고한다.

환경 변수 패턴의 장점

  • 환경 변수를 통해 비밀키를 보관함으로써 세팅 파일을 버전 컨트롤 시스템에 추가할 수 있게 된다.
  • 개발자 개개인이 버전 컨트롤을 관리되는 단일한 settings/local.py를 나눠 쓸 수 있다.
  • 시스템 관리자가 파이썬 코드를 수정하지 않고도 프로젝트 코드를 쉽게 배치할 수 있다.
  • 대부분의 Paas(Platforms-as-a-service)가 설정을 환경 변수를 토앻 이용하기를 추천한다.

로컬 환경에서 환경 변수 세팅하기

# Linux/OSX에서 환경 변수 설정하기
$ export SOME_SECRET_KEY=1c3-cr3am-15-yummy
$ export AUDREY_FREEZER_KEY=y34h-r1ght-d0nt-t0uch-my-1c3-cr34m

운영 환경에서 환경 변수를 세팅하는 방법

서버를 구축하는 방법은 크게 두가지로 나뉜다.

  1. 자체 서버를 운영하는 경우
  2. 프로비저닝 또는 배포를 위한 스크립트 또는 도구(Pass 등)를 이용하는 경우

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.

5.4. 환경 변수를 이용할 수 없을 때

아파치, Nginx등과 같이 환경 변수를 이용할 수 없을 때가 있다. (별도의 환경 변수 시스템이 존재)
이 때 local_settings안티 패턴이 아니라 비밀 파일 패턴(secrets file pattern)을 활용한다.

비밀 파일 패턴 구현 방법은 다음과 같다.

  1. JSON, .env, Config, YAML 또는 XML 중 한가지 포맷을 선택해 비밀 파일을 생성한다.
// secrets.json
{
  "FILENAME": "secrets.json", 
  "SECRET_KEY": "I've got a secret!", 
  "DATABASES_HOST": "127.0.0.1", 
  "PORT": "5432"
}
  1. 비밀 파일을 관리하기 위한 비밀 파일 로더를 간단하게 추가한다.
# 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')
  1. secret 파일의 이름을 .gitignore에 추가한다.

5.5. 여러 개의 requirements 파일 이용하기

각 세팅 파일에 해당하는 requirements파일을 사용할 것을 추천한다. 각 서버에서 그 환경에 필요한 컴포넌트만 설치하자는 의미다.

# 세분화 된 requirements
requirements/
 ├── base.txt
 ├── local.txt
 ├── staging.txt
 ├── production.txt
  1. requirements/base.txt : 모든 환경에 걸쳐 공통적으로 이용할 의존성이 존재
Django==3.2.0
psycopg2-binary==2.8.8
djangorestframework==3.11.0
  1. requirements/local.txt : 개발 환경에서 필요한 패키지 존재
-r base.txt # base.txt requirements 파일 포함
coverage==5.1
django-debug-toolbar==2.2
  1. requirements/ci.txt : 지속적 통합 서버가 이용하는 파일
-r base.txt # base.txt requirements 파일 포함
coverage==5.1
  1. requirements/production.txt : 운영 환경
-r base.txt # base.txt requirements 파일 포함

환경별 requirements의 작성을 완료하였다. 특정 requirementst.txt를 선택해서 설치하는 방법은 다음과 같다.

$ pip install -r requirements/{{file_name}}.txt  

5.6. settings에서 파일 경로 처리하기

다음과 같이 하드코딩된 파일 경로는 피한다.

# 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를 실행해보자.

5.7. 요약

  • 보안에 관계된 사항들을 제외한 모든 요소는 버전 컨트롤로 관리되어야 한다.
  • 상용 운영 환경에서 구현될 프로젝트라면 다수의 settings와 requirements가 유용할 것이다.
  • 환경 변수를 활용할 수 없을 때는 비밀 파일 패턴을 고려해보자.
  • 패키지 버전 또한 버전관리가 되지 않는다면 여러 문제를 일으킬 수 있다. (requirements)

0개의 댓글