시작에 앞서 안내하는 말
이 포스트는 총 5개의 포스트로 이뤄져있으며
해당 포스트를 따라 차근차근 따라하면 AWS로 DJango 프로젝트 배포를 성공적으로 마칠 수 있게
사진을 포함한 명령어를 모두 정리하여 기술해두었다.
주의할 점과 또 부탁할 말은 아래 포스팅의 순서대로 꼭 모든 세팅과 과정을 잘 지키며 진행했으면 좋겠다.
이 5개의 글은 AWS로 DJango 프로젝트를 배포하는데 있어 이전에 내가 고민했던 것과
경험했던 것과 오류의 해결법, 그리고 시행착오로 고생했던 것을 모두 반영하고
또 개념적인 설명도 함께 붙여 설명한다.
5개의 글을 빠짐없이 차근차근 따라온다면 문제없이 장고프로젝트를 배포에 성공할 수 있을 것이다.
잘 진행되길 바라고 또 문제가 있거나 배포 진행방식에 있어 더 좋은점이 있다면
언제든 자유롭게 의견을 댓글로 달아주었으면 좋겠다.
순서
- 배포전 프로젝트 세팅 방법-
- 배포 진행 -
=> 순서를 꼭 잘 지켜 진행하자!
배포전 프로젝트 환경 세팅하기
1. 환경별 settings 분리
=> 다음과 같은 프로젝트 구조에서 프로젝트 폴더를 들어가 settings라는 하위 폴더를 만든다.
1. __init__.py가 있어야 settings라는 폴더 자체를 파이썬 패키지로 인식하기 때문
2. 기존 settings.py를 이 settings 폴더에 집어넣는다.
3. settings.py를 base.py로 바꾼다.
4. 위 사진과 같이 추가로 development.py, local.py, production.py를 생성한다.
=> 위 과정을 순차적으로 진행하여 위 사진과 같은 디렉토리 구조를 만든다.
5. base.py에서 parent를 하나 더 붙여준다.
=> base.py로 기존 settings.py를 settings폴더로 옮겼기에 base_dir의 위치 변동이 생겼다. 하위 폴더로 하나 더
아래로 들어갔으니 사진과 같이 .parent를 더 붙여줘야한다. 꼭!!
base.py는 말 그대로 바뀌지 않을 설정들이 될 것이며 이를 다른 세팅들이 상속할 것이다.
development.py는 개발 환경에서 사용할 것이고 local.py는 작업 환경이고 production.py는 운영환경이다.
6. base.py에서 local.py로 특정 데이터 분리
=> 우리가 작업을 할 때는 상관없지만 실제 서비스를 하거나 개발을 진행할 때를 세팅을 모두 다르게 해야한다.
따라서 환경별 변동이 있을 수 있는 설정들을 다 분리하는 것이다.
=> 사진에 보이는 요소 "그대로" local.py에 옮기도록 하자
=> 그리고 from .base import * 를 상단에 추가하는 것을 잊지말자 -> local.py 세팅으로 돌릴때 base.py 세팅을 상속한다고 했으니
7. INSTALLED_APPS 분리
=> 기존 base.py에 INSTALLED_APPS에 모든 앱들이 정의되어 있었을 텐데 위 사진과 같이
DJANGO_APPS, PROJECT_APPS, THIRD_PARTY_APPS로 다 분리하고 local.py에는
=> 이렇게 작성해주면 된다.
DJANGO_APPS: 장고에서 제공해주는 것들, 기본적으로 있던 것들을 여기로 옮겨준다.
PROJECT_APPS: 우리가 추가한 app들을 모아두면 된다.
THIRD_PARTY_APPS: 지금은 따로 설치한게 없지만 디버거와 같은 것들을 두면 된다. -> 서드파티로써 다른 곳에서 만든 것들을 사용할 때 그런 것들을 다 여기로 옮긴다.
=> 지금은 디버그가 없지만 만약있다면 local.py에서는 THIRD_PARTY_APPS+=['debug_toolbar',] -> 이 부분에 디버그같은 것들을 이렇게 추가해주는 것이 좋다. -> 이렇게 선택적으로 앱들을 사용할건지 말건지 환경별 분리가 가능하다.
이렇게 설정을 하고 난 뒤의 프로젝트 실행은 아래와 같이 settings를 local로 사용할 것이라는 추가 정보 작성을 해주면 된다.
python manage.py runserver --settings=config.settings.local
=> 정상적으로 서버가 잘 작동된다. 나머지 환경들도 세팅해주자
8. base.py에서 production.py로 특정 데이터 분리
=> local.py를 그대로 복사해서 production.py 붙여넣기 한다.
=> 사진과 같이 DEBUG를 FALSE로 바꿔준다. -> 운영하는 서버이기에 디버그가 켜져있으면 안된다. 위험함
=> 또한 production.py 에서는 ALLOWED_HOSTS 에 ip 주소를 넣어줘야한다. -> 추후에 안내할 예정, 사진엔 작성되어있지만 지금은 비워두자
=> 배포를 할때는 STATIC_ROOT를 사용해야한다. 사진과 같은 형식으로 바꿔 작성하자
9. base.py에서 development.py 로 특정 데이터 분리
=> production.py 를 그대로 복사해서 development.py에 붙여넣자
=> 여기서는 디버그를 true로 둬도 되고 false로 둬도 상관없다. 일단 true로 두자
=> 또한 개발환경은 서비스 환경이 아니기에 다시 사진과 같이 STATICFILES_DIRS 형식으로 바꿔야한다.
"이렇게 3개의 환경에 대한 환경설정을 분리하였다."
1. 민감한 정보 settings.py에서 분리하여 secret.json으로 관리하기
settings에는 민감한 정보들이 있다.
secret_key 같은 것들이 그런 것이다.
필자의 경우엔 llm 서비스 개발을 진행하고 이를 테스트 배포로 포스팅을 하고 있기에
open ai api key같은 것들도 민감한 정보에 포함되게 된다.
이러한 민감한 정보들을 따로 분리하여 파일로 만들고 이를 gitignore처리하며 깃허브에 올라가지 못하게 해야한다.
1. secrets.json 파일 생성 및 정보 분리
=> 이렇게 secrets.json파일을 만들어 민감한 정보를 모두 분리하자 -> json 형식으로
2. 이 secrets.json파일을 불러와 읽고 정보를 반영하는 함수를 base.py에 만든다.
import json
from django.core.exceptions import ImproperlyConfigured
secret_file = BASE_DIR / "secrets.json"
with open(secret_file) as file:
secrets = json.loads(file.read())
def get_secret(setting, secrets_dict=secrets):
try:
return secrets_dict[setting]
except KeyError:
error_msg = f'Set the {setting} environment variable'
raise ImproperlyConfigured(error_msg)
=> 이렇게 이 함수를 작성하여 base.py에 작성해주면
=> 이렇게 정보를 파일에서 읽어오게 할 수 있다. -> 민감한 정보는 다 이렇게 처리를 하자
3. .gitignore 생성
이 secrets.json 파일은 깃허브로 올라가면 안된다. 또한 이 파일 말고도 venv와 같이 가상환경은
깃허브에 올라가면 안되기에 아래와 같이 프로젝트 루트에 .gitignore파일을 생성하고 작성해보자
### Django ###
*.log
*.pot
*.pyc
__pycache__/
# local_settings.py
today_orders
db.sqlite3
db.sqlite3-journal
media
secrets.json
### Django.Python Stack ###
# Byte-compiled / optimized / DLL files
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
# Django stuff:
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# pipenv
# poetry
# pdm
#pdm.lock
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PyCharm+all Patch ###
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
=> 복붙하면 됨
2. 우리가 설치한 pip 패키지들을 파일로 분리
1. requirements.txt 생성
=> pip freeze > requirements.txt 를 입력하면 우리가 설치한 pip list들이 다 모이게 된다.
2. 프로젝트 상단에 requirements 폴더 생성
=> requirements 폴더 하위에 base, local, production.txt를 생성하고
=> base.txt에 requirements.txt의 내용을 복사하여 붙여넣는다.
=> local, production.txt에는 두 파일 모두 아래와 같이 -r base.txt 라고 써준다.
"이 분리 역시 환경별 필요에 따라 패키지 설치가 가능할 수 있게 하기 위함이다."
우리가 혼자 개발할 때는 이런 환경적인 요소 분리를 할 필요를 느끼지 못할 수 있지만 대규모 프로젝트 및 회사업무에서는이러한 고민과 환경 분리가 매우매우 중요하다..환경을 분리하고 관리하는 것은 품질의 측면에서 환경을 구축하는 것이기에 환경에 대한 개념을 잘 잡고 분리하는 것이 중요하다.
3. setting 디폴트 값을 production으로 변경
1. config(혹은 프로젝트명) > wsgi.py
=> 위 사진과 같이 wsgi.py에서 os.environ.setdefault 부분에 "config.settings"을 "config.settings.production"으로 바꿔준다.=> 배포할 환경은 production이기에 꼭 붙여줘야함
1. 프로젝트 루트에 manage.py
=> manage.py에도 역시 production을 붙여줘야한다. 꼭!!
4. LOG 추가 <- (이건 필요할 시에 추가하기)
=> 필수는 아니다 필요하다면 추가하기
1. production.py에 아래와 같이 로그를 추가하자
from .base import *
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['43.501.126.899'] #도메인 혹은 ip를...
INSTALLED_APPS = DJANGO_APPS + PROJECT_APPS
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
STATIC_ROOT = BASE_DIR / 'static'
LOG_FILE = '/home/ubuntu/본인프로젝트명->깃허브 레포 이름 넣으면 됨/log/django.log'
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'logfile': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': LOG_FILE,
'when': "midnight", # 매 자정마다
'backupCount': 31,
'formatter': 'standard',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
},
# Loggers (where does the log come from)
'loggers': {
'repackager': {
'handlers': ['console', 'logfile'],
'level': 'DEBUG',
'propagate': True,
},
'django': {
'handlers': ['console', 'logfile'],
'propagate': True,
'level': 'WARN',
},
'django.server': {
'handlers': ['console', 'logfile'],
'propagate': True,
'level': 'INFO',
},
'django.db.backends': {
'handlers': ['console', 'logfile'],
'level': 'WARN',
'propagate': False,
},
'': {
'handlers': ['console', 'logfile'],
'level': 'DEBUG',
},
'raven': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
},
'sentry.errors': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False,
},
'gunicorn.error': {
'level': 'INFO',
'handlers': ['logfile'],
'propagate': True,
},
'gunicorn.access': {
'level': 'INFO',
'handlers': ['logfile'],
'propagate': False,
},
'django.request': {
'handlers': ['logfile'],
'level': 'ERROR',
'propagate': False,
},
}
}
=> LOG_FILE 부분에 "본인프로젝트명" 부분은 "이 프로젝트 코드를 올릴 깃허브 레포"의 이름을 넣어주면 된다. 후에 배포할 때이 위치에 log폴더를 만들예정
배포할 준비를 모두 마쳤다.
배포하기 위한 프로젝트의 세팅을 모두 마쳤다.
이제 아래 링크를 통해 본격적인 aws ec2를 통한 우리의 서비스 배포를 진행해보자
=> 바로 이어서 진행