Django 및 Flask에서 환경 전반의 설정을 간소화하기
Emily Parker
Product Engineer · Leapcell

소개
견고한 웹 애플리케이션을 개발하는 것은 종종 중요한 과제인 소프트웨어 수명 주기의 다양한 단계에 걸쳐 다양한 구성을 관리하는 데 달려 있습니다. 예를 들어, 개발자의 로컬 환경에서 SQLlite 데이터베이스에 연결하거나 상세한 디버깅을 활성화하는 것과 같이 완벽하게 작동하는 것이 프로덕션 환경에서는 재앙이 될 수 있습니다. 프로덕션에서는 고도로 최적화된 데이터베이스 연결, 견고한 로깅, 엄격한 보안 설정 및 공개에 노출되는 최소한의 디버깅 정보가 필요합니다. 마찬가지로, 테스트 환경은 격리되고 반복 가능한 테스트를 보장하기 위해 특정 구성 집합을 요구합니다. 이러한 차이를 무시하면 좌절스러운 버그, 보안 취약점 및 배포 문제가 발생합니다. 이 글에서는 Python의 인기 있는 웹 프레임워크인 Django 및 Flask에서 이러한 서로 다른 구성을 우아하게 처리하는 실용적이고 효과적인 전략을 탐색하여 개발에서 프로덕션으로의 원활한 전환을 보장합니다.
핵심 개념 및 구현 전략
Django 및 Flask의 특정 내용을 자세히 살펴보기 전에 효과적인 구성 관리에 기본이 되는 몇 가지 핵심 개념을 확립해 보겠습니다.
핵심 용어
- 환경 변수: 실행 중인 프로세스가 컴퓨터에서 동작하는 방식에 영향을 줄 수 있는 동적으로 명명된 값입니다. 일반적으로 API 키, 데이터베이스 자격 증명과 같은 민감한 정보 또는 애플리케이션 코드베이스에 하드 코딩하지 않고 환경별 설정을 저장하는 데 사용됩니다.
- 설정/구성 파일: 이러한 파일(Django의
settings.py
또는 Flask의 사용자 지정.py
또는.ini
파일)에는 데이터베이스 연결, 활성화된 앱, 미들웨어 및 로깅 수준과 같이 애플리케이션의 동작을 결정하는 키-값 쌍이 포함됩니다. SECRET_KEY
: Django(및 종종 Flask 세션 관리에 사용됨)의 중요한 보안 설정으로, 암호화 서명에 사용됩니다. 프로덕션 환경에서는 각 환경마다 비밀로 유지하고 고유하게 유지해야 합니다.- 디버깅 상태: 디버깅 정보가 표시되는지 여부를 제어하는 부울 플래그(Django의
DEBUG
, Flask의DEBUG
)입니다. 프로덕션에서는 항상False
여야 합니다.
구성에 대한 일반 원칙
- 비밀 정보를 버전 관리에 커밋하지 마십시오: 데이터베이스 암호, API 키,
SECRET_KEY
와 같은 민감한 정보는 하드 코딩하거나 Git에 커밋하면 안 됩니다. - 환경별 설정을 분리하십시오: 개발, 테스트, 프로덕션 간에 변경되는 구성은 핵심 애플리케이션 논리와 분리해야 합니다.
- 민감한 데이터의 경우 환경 변수를 우선적으로 사용하십시오: 환경 변수는 런타임에 애플리케이션에 민감한 데이터를 주입하는 안전하고 유연한 방법을 제공합니다.
- 적절한 기본값 사용: 개발에 적합한 기본 구성을 제공한 다음 다른 환경에 대해 재정의합니다.
Django 구성 전략
Django의 내장 설정 관리는 강력하지만 다중 환경 배포를 위해 주의 깊게 구조화해야 합니다. 일반적으로 권장되는 접근 방식은 설정을 패키지로 분리하는 것입니다.
프로젝트 구조가 다음과 같다고 가정해 보겠습니다.
myproject/
├── manage.py
├── myproject/
│ ├── __init__.py
│ ├── urls.py
│ ├── wsgi.py
│ └── settings/
│ ├── __init__.py
│ ├── base.py
│ ├── development.py
│ ├── production.py
│ └── test.py
└── myapp/
└── ...
1. myproject/settings/base.py
:
이 파일에는 모든 환경에 적용되는 모든 공통 설정이 포함됩니다.
# myproject/settings/base.py import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! # Use environment variable for SECRET_KEY SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'default-insecure-key-for-dev-only') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False # Default to False, overridden in development.py ALLOWED_HOSTS = [] # Overridden in specific environments # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'myapp', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'myproject.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'myproject.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } AUTH_PASSWORD_VALIDATORS = [ # ... default validators ... ] LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True STATIC_URL = '/static/' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
2. myproject/settings/development.py
:
이 파일은 base.py
를 가져와 로컬 개발을 위한 설정을 재정의합니다.
# myproject/settings/development.py from .base import * DEBUG = True ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] # Use a simpler database for development DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Add any development-specific apps or tools INSTALLED_APPS += [ 'debug_toolbar', ] MIDDLEWARE += [ 'debug_toolbar.middleware.DebugToolbarMiddleware', ] # Django Debug Toolbar configuration INTERNAL_IPS = [ '127.0.0.1', ] LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'INFO', }, 'myapp': { 'handlers': ['console'], 'level': 'DEBUG', # More verbose logging for your app in dev }, }, }
3. myproject/settings/production.py
:
이 파일은 base.py
를 가져와 프로덕션 준비 설정을 설정합니다.
# myproject/settings/production.py from .base import * DEBUG = False # Load SECRET_KEY from environment variable for security SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') if not SECRET_KEY: raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable not set') ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',') if not ALLOWED_HOSTS: raise ImproperlyConfigured('DJANGO_ALLOWED_HOSTS environment variable not set') # Production database settings (e.g., PostgreSQL) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ.get('POSTGRES_DB_NAME'), 'USER': os.environ.get('POSTGRES_DB_USER'), 'PASSWORD': os.environ.get('POSTGRES_DB_PASSWORD'), 'HOST': os.environ.get('POSTGRES_DB_HOST'), 'PORT': os.environ.get('POSTGRES_DB_PORT', '5432'), } } # Static file storage and serving STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # Recommended for production # Security settings CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True SECURE_SSL_REDIRECT = True SECURE_HSTS_SECONDS = 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SECURE_BROWSER_XSS_FILTER = True X_FRAME_OPTIONS = 'DENY' # Logging for production (e.g., to file or external service) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, }, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': '/var/log/myproject/myproject.log', # Adjust path as needed 'maxBytes': 1024*1024*5, # 5MB 'backupCount': 5, 'formatter': 'verbose', }, 'console': { # Still useful for containerized environments 'class': 'logging.StreamHandler', 'formatter': 'verbose', }, }, 'loggers': { 'django': { 'handlers': ['file', 'console'], 'level': 'INFO', 'propagate': True, }, 'myapp': { 'handlers': ['file', 'console'], 'level': 'WARNING', # Less verbose for production unless critical 'propagate': False, }, }, }
4. myproject/settings/test.py
:
개발과 비슷하지만 격리된 테스트를 위해 매우 구체적인 변경이 이루어집니다.
# myproject/settings/test.py from .base import * DEBUG = False # Tests should run as close to production as possible, without debug info # Use an in-memory SQLite database for fast, isolated tests DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', } } # Speed up password hashing during tests PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.MD5PasswordHasher', ] SECRET_KEY = 'test-insecure-key' # Safe for test
5. 올바른 설정 로드 방법:
Django는 DJANGO_SETTINGS_MODULE
환경 변수를 사용하여 설정을 로드합니다.
- 개발:
export DJANGO_SETTINGS_MODULE=myproject.settings.development
- 프로덕션:
export DJANGO_SETTINGS_MODULE=myproject.settings.production
- 테스트:
export DJANGO_SETTINGS_MODULE=myproject.settings.test
웹 서버 구성(예: Gunicorn, uWSGI) 또는 Django 명령을 실행할 때 직접 설정할 수 있습니다.
# For development DJANGO_SETTINGS_MODULE=myproject.settings.development python manage.py runserver # For production (via Gunicorn example) DJANGO_SETTINGS_MODULE=myproject.settings.production gunicorn myproject.wsgi:application
Flask 구성 전략
Flask는 구성을 관리하는 방법에 대한 유연성을 제공합니다. 일반적인 접근 방식은 Python 클래스와 환경 변수를 활용하는 것입니다.
구조가 다음과 같다고 가정해 봅시다.
myflaskapp/
├── run.py
├── config.py
└── myflaskapp/
├── __init__.py
└── views.py
1. config.py
:
이 파일은 기본, 개발, 테스트 및 프로덕션 구성 클래스를 정의합니다.
# config.py import os from datetime import timedelta class Config: """Base configuration settings.""" SECRET_KEY = os.environ.get('FLASK_SECRET_KEY', 'dev-secret-key-insecure') SQLALCHEMY_TRACK_MODIFICATIONS = False PERMANENT_SESSION_LIFETIME = timedelta(days=7) LOG_LEVEL = 'INFO' class DevelopmentConfig(Config): """Development configuration.""" DEBUG = True ENV = 'development' SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \ 'sqlite:///' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'dev.db') LOG_LEVEL = 'DEBUG' class TestingConfig(Config): """Testing configuration.""" DEBUG = True # Keep debug true for detailed error reporting during tests ENV = 'testing' TESTING = True # Flask-specific flag SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # In-memory database for tests LOG_LEVEL = 'WARNING' SECRET_KEY = 'test-secret-key' # Safe for test class ProductionConfig(Config): """Production configuration.""" DEBUG = False ENV = 'production' SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') if SQLALCHEMY_DATABASE_URI is None: raise ValueError("DATABASE_URL environment variable not set for production.") # Override SECRET_KEY from environment for production SECRET_KEY = os.environ.get('FLASK_SECRET_KEY') if SECRET_KEY is None: raise ValueError("FLASK_SECRET_KEY environment variable not set for production.") # Logging: For production, typically send to file or a log management service LOG_LEVEL = 'ERROR' # Only high-severity logs
2. myflaskapp/__init__.py
:
이 파일은 Flask 앱을 초기화하고 적절한 구성을 로드합니다.
# myflaskapp/__init__.py from flask import Flask import os import logging from logging.handlers import RotatingFileHandler def create_app(): app = Flask(__name__) # Determine which config to load based on FLASK_ENV environment variable # It's common to default to DevelopmentConfig if FLASK_ENV is not set config_name = os.environ.get('FLASK_ENV', 'development') if config_name == 'production': app.config.from_object('config.ProductionConfig') elif config_name == 'testing': app.config.from_object('config.TestingConfig') else: # Default to development app.config.from_object('config.DevelopmentConfig') # Example of integrating a database if you're using one like SQLAlchemy # from flask_sqlalchemy import SQLAlchemy # db = SQLAlchemy(app) # Configure logging based on the environment if not app.debug and not app.testing: # Production logging example (to a file) if not os.path.exists('logs'): os.mkdir('logs') file_handler = RotatingFileHandler('logs/myflaskapp.log', maxBytes=10240, backupCount=10) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) file_handler.setLevel(logging.ERROR) # Use app.config.get('LOG_LEVEL', 'ERROR') app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) # Use app.config.get('LOG_LEVEL', 'INFO') app.logger.info('MyFlaskApp startup') elif app.debug: # Development logging to console app.logger.setLevel(logging.DEBUG) app.logger.info('MyFlaskApp development startup') from . import views app.register_blueprint(views.bp) return app
3. run.py
(또는 이와 유사한 진입점):
# run.py from myflaskapp import create_app app = create_app() if __name__ == '__main__': app.run()
4. 올바른 설정 로드 방법:
Flask는 일반적으로 FLASK_ENV
환경 변수를 사용하며, 이는 development
, production
또는 testing
이 될 수 있습니다.
- 개발:
export FLASK_ENV=development
- 프로덕션:
export FLASK_ENV=production
- 테스트:
export FLASK_ENV=testing
그런 다음 애플리케이션을 실행합니다.
# For development export FLASK_ENV=development python run.py # For production (via Gunicorn example) export FLASK_ENV=production export DATABASE_URL="postgresql://user:password@host/dbname" export FLASK_SECRET_KEY="a-very-long-and-secure-random-string" gunicorn 'myflaskapp:create_app()'
환경 변수 관리
Django 또는 Flask를 사용하든 관계없이 환경 변수를 안전하게 관리하는 것이 무엇보다 중요합니다.
-
개발용:
.env
파일과python-dotenv
와 같은 라이브러리를 사용합니다.pip install python-dotenv
- 프로젝트 루트에
.env
파일 생성(예:myproject/.env
또는myflaskapp/.env
):DJANGO_SECRET_KEY='your-dev-secret-key-here' POSTGRES_DB_NAME='myproject_dev' FLASK_SECRET_KEY='your-dev-secret-key-here' DEV_DATABASE_URL='sqlite:///dev.db'
.env
를.gitignore
파일에 추가합니다.- Django 설정(
settings.py
) 또는 Flask 구성(config.py
/__init__.py
)에서 로드합니다.# In Django settings/base.py or Flask config.py/init.py from dotenv import load_dotenv load_dotenv() # This loads variables from .env into os.environ # Then access them as usual SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
-
프로덕션용: 환경 변수는 일반적으로 호스팅 제공업체(예: Heroku 구성 변수, Kubernetes 시크릿, AWS EC2 환경 변수, Docker compose
env_file
)에서 설정됩니다. 프로덕션에서는.env
파일을 사용하지 마십시오. 로컬 편의를 위한 것입니다.
애플리케이션 시나리오
위에 설명된 구성은 광범위한 시나리오에 적합합니다.
- 로컬 개발: 상세한 로깅과 사용하기 쉬운 로컬 데이터베이스를 사용한 빠르고 반복적인 변경.
- 자동화된 테스트: 속도와 부작용 방지를 보장하기 위해 인메모리 또는 임시 데이터베이스를 사용한 격리되고 반복 가능한 테스트.
- 스테이징/프로덕션 전 단계: 배포 전 최종 확인을 허용하는 외부 데이터베이스 및 서비스를 사용하는 프로덕션과 거의 동일한 복제본.
- 프로덕션: 외부 서비스, 견고한 모니터링 및 최소한의 디버깅 노출을 갖춘 안전하고 최적화되며 성능이 뛰어난 설정.
결론
Django 및 Flask를 사용하는 안정적이고 안전한 웹 애플리케이션 구축의 초석은 다양한 환경에 걸쳐 구성을 효과적으로 관리하는 것입니다. 환경별 설정을 분리하고, 민감한 데이터에 환경 변수를 활용하고, 구성 파일을 신중하게 구조화함으로써 강력하고 유지 관리 가능한 시스템을 구축하게 됩니다. 잘 구성된 구성 전략은 배포 위험을 크게 줄이고 애플리케이션 안정성을 향상시킵니다.