Цей матеріал для вас, якщо ви використовуєте фреймворк Django, хочете глибше розібратися у конфігурації параметрів проекту, а також порівняти переваги та недоліки різних підходів до налаштувань. Окрім того, у статті ви знайдете рекомендації щодо інструментів, найкращих практик і архітектурних рішень, які перевірені часом та успішними проектами.
Управління налаштуваннями в Django: проблеми
-
Різні середовища. Зазвичай ви маєте справу з різними середовищами:
local
,dev
,ci
,qa
,staging
,production
тощо. Кожне з них має особливі налаштування (наприклад,DEBUG = True
, більш детальне логування, додаткові застосунки, mock-дані тощо). Вам треба знайти таке рішення, що об'єднало б усі ці налаштування. -
Конфіденційні дані. У кожному Django-проекті є
SECRET_KEY
. Окрім того, у вас можуть бути паролі до БД та токени для сторонніх API, таких як Amazon або Twitter. Подібні дані не можуть зберігатися у VCS (version control system) . -
Поширення налаштувань між членами команди. Вам потрібен загальний підхід для уникнення людських помилок при роботі з налаштуваннями. Наприклад, розробник може додати сторонній застосунок або інтеграцію API, але не вказати потрібних параметрів. У великих (і навіть середніх) проектах це призводить до проблем.
-
Налаштування Django — код на Python. Це і перевага, і недолік. Так, ви отримаєте гнучкість, але на відміну від пар ключ/значення, налаштування у
settings.py
можуть мати нетривіальну логіку.
Конфігурація параметрів: різні підходи
Не існує універсального способу сконфігурувати параметри Django без написання коду. Але книги та різноманітні проекти допомагають обрати найкращий спосіб. Коротко оглянемо найбільш популярні підходи та визначимо їх сильні та слабкі сторони.
settings_local.py
Це трохи застарілий спосіб, однак він все ще знаходить своє застосування. Основний сенс методу — розширити усі залежні від середовища параметри у файлі settings_local.py
, який ігнорується VCS. Розглянемо приклад.
settings.py
ALLOWED_HOSTS = ['example.com']
DEBUG = False
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'production_db',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'db.example.com',
'PORT': '5432',
'OPTIONS': {
'sslmode': 'require'
}
}
}
...
from .settings_local import *
settings_local.py
ALLOWED_HOSTS = ['localhost']
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'local_db',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
Переваги:
- Секретні дані не потрапляють у VCS.
Недоліки:
-
settings_local.py
не знаходиться у VCS, тому ви можете втратити деякі налаштування середовища Django. - Файл з налаштуваннями Django містить код на Python, тому зміст
settings_local.py
може бути не дуже зрозумілим. - Щоб поширити конфігурацію за замовчуванням між розробниками, вам необхідно додати файл
settings_local.example
у VCS.
Окремий файл з налаштуваннями для кожного середовища
Такий підхід — лише розширення попереднього. З окремими файлами ви зможете зберігати всі конфігурації у VCS та поширювати налаштування за замовчуваннями між розробниками.
Створюємо пакет settings
із таким вмістом:
settings/
├── __init__.py
├── base.py
├── ci.py
├── local.py
├── staging.py
├── production.py
└── qa.py
Вміст settings/local.py
:
from .base import *
ALLOWED_HOSTS = ['localhost']
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'local_db',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
Щоб запустити проект з певною конфігурацією, необхідно задати додатковий параметр:
python manage.py runserver --settings=settings.local
Переваги:
- Усі середовища знаходяться у VCS.
- Легко поширювати налаштування між розробниками.
Недоліки:
- Необхідно знайти спосіб обробки секретних паролів та токенів.
- «Наслідування» налаштувань, найімовірніше, буде важко відстежувати та підтримувати.
Змінні середовища
Для розв'язання проблем з конфіденційними даними можна використовувати змінні середовища:
import os
SECRET_KEY = os.environ['SECRET_KEY']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DATABASE_NAME'],
'HOST': os.environ['DATABASE_HOST'],
'PORT': int(os.environ['DATABASE_PORT']),
}
}
У наведеному прикладі з використанням os.environ є декілька проблем:
- Треба обробляти виключення
KeyError
. - Треба вручну перетворювати типи (дивіться використання
DATABASE_PORT
).
Щоб виправити проблему з KeyError
, можна створити власну користувацьку обгортку.
Переконаємось на прикладі:
import os
from django.core.exceptions import ImproperlyConfigured
def get_env_value(env_variable):
try:
return os.environ[env_variable]
except KeyError:
error_msg = 'Set the {} environment variable'.format(var_name)
raise ImproperlyConfigured(error_msg)
SECRET_KEY = get_env_value('SECRET_KEY')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': get_env_value('DATABASE_NAME'),
'HOST': get_env_value('DATABASE_HOST'),
'PORT': int(get_env_value('DATABASE_PORT')),
}
}
Ви також можете встановити значення за замовчуванням та додати перетворення типів. Насправді ж не потрібно самостійно створювати таку обгортку, тому що можна скористатися сторонніми бібліотеками (далі про це детальніше).
Переваги:
- Відокремлення конфігурації від коду.
- Один код для усіх середовищ.
- Відсутність наслідування у налаштуваннях, більш чистий та узгоджений код.
- Існує теоретичне пояснення, чому варто використовувати змінні середовища, про яке ми поговоримо далі.
Недоліки:
- Необхідно організувати обробку поширення однакової конфігурації між розробниками.
Застосунок дванадцяти факторів
12 факторів — перелік рекомендацій щодо створення розподілених веб-застосунків, які було б легко розгортати та масштабувати у хмарі. Вони були розроблені Heroku, добре відомою хмарною платформою.
Відповідно до назви, перелік складається з 12 частин:
- кодова база,
- залежності,
- конфігурація,
- сторонні служби,
- збірка, реліз, виконання,
- процеси,
- прив'язка прототипів,
- паралелізм,
- утилізованість,
- паритет розробки/роботи застосунку,
- логування,
- завдання адміністрування.
Кожен пункт описує рекомендований спосіб реалізації певних аспектів проекту. Деякі з вказаних факторів контролюються такими інструментами, як Django, Python, pip. Інші ж — за допомогою шаблонів проектування або налаштувань інфраструктури. В контексті статті нас цікавитиме одна частина — конфігурація.
Основне правило: зберігати конфігурацію у середовищі виконання. Тоді ми отримаємо чітке розділення конфігурації та коду.
Більше інформації про описані концепції за посиланням.
django-environ
Вище ми вже з'ясували, що середовище — ідеальне місце для зберігання налаштувань.
Прийшов час поговорити про відповідні інструменти.
Написання коду з os.environ
іноді може бути непростим і вимагати додаткових зусиль для обробки помилок. Чудова альтернатива — django-environ
.
Технічно це поєднання таких складників:
Вказані застосунки передбачають функціональний API для отримання значень зі змінних середовища або текстових файлів, перетворення типів тощо. Розглянемо декілька прикладів.
Файл settings.py
до:
import os
SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
DEBUG = True
TEMPLATE_DEBUG = DEBUG
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'production_db',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'db.example.com',
'PORT': '5432',
'OPTIONS': {
'sslmode': 'require'
}
}
}
MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets')
MEDIA_URL = 'media/'
STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
STATIC_URL = 'static/'
SECRET_KEY = 'Some-Autogenerated-Secret-Key'
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': '127.0.0.1:6379/1',
}
}
Файл settings.py
після:
import environ
root = environ.Path(__file__) - 3 # get root of the project
env = environ.Env()
environ.Env.read_env() # reading .env file
SITE_ROOT = root()
DEBUG = env.bool('DEBUG', default=False)
TEMPLATE_DEBUG = DEBUG
DATABASES = {'default': env.db('DATABASE_URL')}
public_root = root.path('public/')
MEDIA_ROOT = public_root('media')
MEDIA_URL = env.str('MEDIA_URL', default='media/')
STATIC_ROOT = public_root('static')
STATIC_URL = env.str('STATIC_URL', default='static/')
SECRET_KEY = env.str('SECRET_KEY')
CACHES = {'default': env.cache('REDIS_CACHE_URL')}
Файл .env
:
DEBUG = True
DATABASE_URL = postgres://user:password@db.example.com:5432/production_db?sslmode=require
REDIS_CACHE_URL = redis://user:password@cache.example.com:6379/1
SECRET_KEY = Some-Autogenerated-Secret-Key
Файлова структура налаштувань
Замість того, щоб групувати налаштування за середовищами, ви можете згрупувати їх за джерелами: Django, сторонні застосунки (Celery, DRF тощо) та ваші користувацькі налаштування.
Тоді це матиме такий вигляд:
project/
├── apps/
├── settings/
│ ├── __init__.py
│ ├── django.py
│ ├── project.py
│ └── third_party.py
└── manage.py
Вміст __init__.py
файлу:
from .django import * # Усі налаштування стосовно Django
from .third_party import * # Celery, Django REST Framework та інші сторонні бібліотеки
from .project import * # Ваші користувацькі налаштування
Кожен модуль можна організувати як пакет і ще більш детально:
project/
├── apps/
├── settings/
│ ├── project
│ │ ├── __init__.py
│ │ ├── custom_module_foo.py
│ │ ├── custom_module_bar.py
│ │ └── custom_module_xyz.py
│ ├── third_party
│ │ ├── __init__.py
│ │ ├── celery.py
│ │ ├── email.py
│ │ └── rest_framework.py
│ ├── __init__.py
│ └── django.py
└── manage.py
Угоди щодо іменування
Іменування змінних — одна з найскладніших частин розробки. Те ж саме й з іменуванням параметрів налаштувань. Варто дотримуватись деяких правил для користувацьких (проектних) налаштувань, зокрема:
- Давати налаштуванням осмислені назви.
- Завжди використовувати префікс з назвою проекту для користувацьких налаштувань.
- Писати коментарі для пояснення.
Так робити не треба:
API_SYNC_CRONTAB = env.str('API_SYNC_CRONTAB')
Набагато краще так:
# Run job for getting new tweets.
# Accept string in crontab format. By default: every 30 minutes.
MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB = env.str(
'MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB', default='30 * * * *'
)
Замініть MYAWESOMEPROJECT
на справжню назву вашого проекту.
Конфіги Django: найкращі практики
- Зберігайте налаштування у змінних середовища.
- Вказуйте значення за замовчуванням для конфігів у продакшені (за винятком секретних ключів та токенів).
- Не хардкодьте конфіденційні налаштування, і не розміщуйте їх у VCS.
- Розбивайте налаштування на групи: Django, сторонні бібліотеки, проект.
- Виконуйте умови іменування користувацьких (проектних) налаштувань.
Відео
З цією темою я робив Lightning talk на DjangoCon Europe 2019 у Копенгагені (00:00 - 4:46).
Висновок
Файл з налаштуваннями — невелика, але дуже важлива частина будь-якого проекту на Django. Якщо ви не організуєте його добре, у майбутньому матимете багато проблем на всіх стадіях розробки. З іншого боку: добре організовані налаштування стануть надійною основою для вашого проекту, який згодом можна буде розширювати та масштабувати.
Використовуючи підхід зі змінними середовища, ви з легкістю зможете перейти від монолітної до мікросервісної архітектури, огорнути свій проект у контейнери Docker, а також розгорнути його на будь-якому VPS або на хмарній платформі, на зразок Amazon, Google Cloud або власному кластері Kubernetes.
Ще немає коментарів