Les réglages d'un projet Django sont définis dans un module qui est configurable par la variable d'environnement DJANGO_SETTINGS_MODULE. Cela permet, pour un même projet Django, d'avoir plusieurs réglages. Comment faire pour l'utiliser à son avantage tout en restant dans les bonnes pratiques ?
Les réglages d’une application Django sont stockés dans un module (un fichier python). Il s’agit d’une suite de variables pythons qui seront rendus accessibles partout dans l’application via le module django.conf.settings
(grâce à un système lazy)1
De son côté, le 12-factor2 est un ensemble de bonnes pratiques pour développer des applications logicielles modernes et évolutives.
Un de ses principes incite à stocker tous les réglages dans des variables d’environnements3.
Django offre la possibilité de séparer facilement les réglages de prod des réglages de dev, en faisant des modules (fichiers) différents. Il est malgré tout fortement conseillé de séparer les valeurs sensibles (mots de passes, users, urls des webservices, etc.), dans des variables d’environnements par exemple.
L'utilisation de plusieurs fichiers de configuration pourrait sembler contraire à la préconisation du 12-factor. Il est pourtant possible d’utiliser cette mécanique tout en restant dans l’esprit de la préconisation.
Lorsque l’application Django est lancée (via manage.py
ou via un serveur web), elle va récupérer la valeur d'une variable d'environnement DJANGO_SETTINGS_MODULE
, et va charger le module de configuration en fonction de la valeur de cette variable.
Si cette variable est absente, alors Django basculera sur une valeur par défaut. Voir, par exemple, le contenu du fichier manage.py
ligne 3 :
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
De plus, il est possible de spécifier le fichier de configuration à utiliser lorsqu’on lance la commande :
./manage.py shell --settings=my_custom_python_package.anywhere.in.the.world.settings
Par défaut, lorsque l’on crée un projet Django, un fichier de configuration par défaut est créé et l'on n’a pas besoin de modifier ces valeurs.
Pourquoi Django a mis en place un système de modules de configuration (DJANGO_SETTINGS_MODULE
), bien que l'un des principes du 12-factors soit : "Store config in the environment" ?
Les modules de configuration Django (
DJANGO_SETTINGS_MODULE
) existent pour faciliter la gestion et l'accès aux paramètres spécifiques d'une application Django. Bien que le principe "Store config in the environment" du 12-factor recommande de stocker les configurations dans l'environnement, Django utilise un autre mécanisme pour gérer ses configurations.L'idée derrière le 12-factor est d'externaliser la configuration de l'application en utilisant des variables d'environnement. Cela permet de séparer les paramètres spécifiques de l'environnement (comme les clés d'API, les URL de base de données, etc.) de l'application elle-même. Cette approche est très flexible et recommandée pour la plupart des applications.
Cependant, Django est un framework web puissant et complexe qui offre une grande flexibilité de configuration. Il propose une variété de paramètres, y compris ceux qui sont spécifiques au framework et non directement liés à l'environnement. Ces paramètres peuvent concerner des fonctionnalités spécifiques de Django, telles que les applications installées, les modèles de base de données, les fichiers statiques, etc.
Plutôt que de les stocker tous dans l'environnement, Django utilise un module de configuration dédié (
DJANGO_SETTINGS_MODULE
) pour gérer ces paramètres spécifiques. Ce module pointe vers un fichier Python qui contient toutes les configurations nécessaires pour faire fonctionner l'application Django.Ainsi, les paramètres généraux de configuration, tels que les clés secrètes, les paramètres de base de données et d'autres options importantes, peuvent être stockés dans des variables d'environnement, conformément aux principes du 12-factor. En revanche, les paramètres spécifiques à Django peuvent être définis dans le module de configuration Django lui-même.
En résumé, les modules de configuration Django (
DJANGO_SETTINGS_MODULE
) existent pour gérer les paramètres spécifiques de Django, qui peuvent être distincts des variables d'environnement. Cela permet à Django de fournir une configuration flexible et spécifique au framework tout en respectant les principes de base du 12-factor en stockant les paramètres sensibles dans l'environnement.
Tout d’abord, il existe tout un vocabulaire finalement assez peu balisé : propriétés
, paramètres
, réglages
, configuration
ou encore secret
. Ces termes ne possèdent pas de définitions complétement arrêtées, selon les contextes, et sont parfois synonymes, parfois non. Mixés avec les termes environnement
, ou encore instance
, on se retrouve avec un joyeux bordel largement sujet à quiproquo au sein des équipes.
Le premier effort est d’harmoniser le vocabulaire à l'intérieur de l'équipe.
En ce qui me concerne, je parle de propriété
(properties
) pour parler des valeurs propres à l’environnement
sur lequel l'application tourne4, et de paramètres
(settings
), pour parler des valeurs qui configurent l'application Django.
J'évite le terme variable d'environnements
, car techniquement, les propriétés peuvent venir de différentes sources. On peut, par exemple, choisir une solution spécifique sécurisée pour gérer les secrets, comme Vault. Les propriétés peuvent donc provenir de plusieurs sources.
Le choix du terme paramètres
(settings
) est choisi en référence au nom du module python choisi par Django (django.conf.settings
). Pour un réglage en particulier, j’ai choisi la traduction paramètre
du terme setting
, alors que pour l’ensemble des paramètres, le fameux DJANGO_SETTINGS_MODULES
, j'ai choisi la traduction module de configuration
, que je trouve plus joli en français que “module de paramètres”. Un module de configuration contient donc des paramètres.
C’est évidemment un choix personnel et l’important est d’adopter un vocabulaire commun dans l'équipe.
Quant aux notions d’environnements
et d'instance
, j’ai coutume de dire qu’une application Django qui tourne représente une instance
, elle tourne sur un environnement
. Du point de vue de l’application, un environnement ne représente pas un serveur en particulier, mais un type de serveur (dev, test, ci, prod), un contexte. L'instance d'un serveur est différente d'une instance Django en ce sens qu'une instance de serveur peut servir plusieurs instances Django.
Par exemple :
notre instance Django “staging“ tourne sur un environnement de pré-production, sur notre serveur hors-prod-6.my-company
Selon moi, dans un contexte Django (mais pas que), il existe une différence fondamentale entre les propriétés et les paramètres de l’application. Le rapport entre les paramètres et les propriétés ne sont pas toujours de 1 pour 1. C'est-à-dire que chaque paramètre individuel de l’application n’a pas son équivalent en propriété, et qu’une propriété peut parfois affecter plusieurs paramètres.
Les "propriétés" correspondent aux informations spécifiques à l'environnement, telles que les clés d'API, les URL de base de données ou d'autres variables qui peuvent varier d'un environnement à l'autre.
Les "paramètres" vont au-delà de la simple affectation de valeur selon l'environnement. Ils peuvent affecter le comportement et la logique de l'application en spécifiant des options telles que les backends utilisés ou la mise en place de limites de l'utilisation des APIs (la limite pouvant elle-même être configurée via une propriété).
On pourrait jouer avec des conditions basées sur la valeur de la propriété. Par exemple, si nous avons la propriété DEBUG
qui est égale à True
, alors nous mettons une condition qui vient changer certains réglages :
DEBUG = get_property('DEBUG') # récupère la var d'env DEBUG
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': get_property('DEFAULT_THROTTLE_RATES'),
},
}
if DEBUG:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar", "django_extensions"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
INTERNAL_IPS = ["127.0.0.1",]
REST_FRAMEWORK['DEFAULT_THROTTLE_CLASSES'] = []
REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] = []
# faire suivre tous les autres réglages spécifiques au mode de debug,
}
Pour des soucis de lisibilité, je préfère m’appuyer sur le système de module de configuration de Django. Avec une seule variable d’environnement (DJANGO_SETTINGS_MODULE
), je peux avoir quelque chose de facile à lire. Chaque différence de comportement est explicitement documentée en fonction de son contexte.
En ce qui concerne la mise en place, je garde tous les réglages communs dans un fichier base.py
, qui va contenir tous les paramètres qui seront toujours vrais quels que soient les environnements (et qui peuvent s’appuyer sur des propriétés qui vont dépendre de l’environnement). Concrètement, je renomme le fichier settings.py
généré par Django en base.py
et je le déplace dans un répertoire settings
.
Ensuite, je crée un fichier dev.py
dans lequel je vais mettre tous les paramètres spécifiques au mode de travail de dev :
from base import *
DEBUG = True # Je fais le choix de hardcoder le mode debug, puisqu'il est une conséquence de la valeur de `DJANGO_SETTINGS_MODULE`
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar", "django_extensions"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
INTERNAL_IPS = ["127.0.0.1",]
Et, par exemple, un module de configuration prod.py
destiné aux environnements de production :
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': get_property('DEFAULT_THROTTLE_RATES'), # la propriété en elle même dépend de l'environnement
},
}
# On peut retrouver des configurations spécifiques à Heroku par exemple
Il est courant d'avoir des différences de comportement dans une application Django en fonction de l'environnement dans lequel elle est exécutée, que ce soit en développement, en production ou en test par exemple. Voici quelques raisons, non exhaustives.
On peut avoir besoin de modifier le comportement selon que l’on est sur tel ou tel environnement.
Un exemple assez basique est Sentry5 : Lorsqu’on est sur un environnement de test ou de développement, on ne souhaite pas attraper les erreurs pour les envoyer à Sentry. Ainsi, on ne configurera Sentry que dans les modules de configuration liés à la production, éventuellement à la pré-production (avec des propriétés différentes).
Lorsqu’on travaille en développement, on peut avoir envie de récupérer les emails envoyés directement en console ou en fichier, plutôt que de faire un vrai call SMTP.
Certaines dépendances ne sont présentes que sur certains environnements. La dépendance sentry
n'est pas installée en développement, la dépendance django_debug_toolbar
n'est installée qu'en développement.
Pour ces raisons, on n’a pas envie de charger des modules faisant référence à ces dépendances absentes. C’est juste… plus propre…
À noter donc que le cas de Sentry vu abordé dans le point 1 rentre aussi dans cette catégorie.
Il est courant de maximiser la sécurité et la performance dans un environnement de production. En revanche, en dev ou en test, on voudra souvent assouplir ces règles afin de faciliter le développement.
Par exemple, on peut désactiver le SSL localement, ou utiliser un moteur d’authentification local pour éviter de devoir dépendre d’un service externe.
Pour le testing, on va vouloir désactiver le cache afin de s’assurer que les résultats comparés soient les derniers calculés.
’un des trucs assez pratiques avec l’utilisation des modules de configuration Django, c’est que ça offre la possibilité de créer son propre module de configuration Django, destiné à son usage personnel, dans le but de tester différentes choses pendant le développement.
Ce fichier peut être versionné ou non, l’intérêt résidant dans le partage de connaissance sur les réglages entre les membres de l'équipe.
Les cas d’utilisation sont nombreux :
Le fait de les partager avec les autres membres de l'équipe peut aider à débugger.
Dans tous les cas, il ne faut jamais y mettre directement des credentials et autres données sensibles, bien évidemment.
L’utilisation de modules de configuration Django n’est pas nécessairement contradictoire avec les principes de 12-factors. En effet, les réglages sensibles et les valeurs dépendant uniquement de l’environnement doivent être extraites dans des propriétés dépendant de l’environnement, alors que les paramètres illustrant des différences de comportement dans l’application doivent être séparées dans des modules de configuration différents.
Cependant, il est possible d’utiliser les modules de configuration Django d’une manière incompatible avec les 12-factors. Voici quelques anti-patterns :
En évitant ces pièges, alors on pourra dire que l’application Django est conforme aux principes du 12-factors.
Les 12-factors sont des recommandations ou des principes à suivre plutôt que des règles strictes et arbitraires. Ils ont été formulés comme un ensemble de bonnes pratiques pour le développement et le déploiement d'applications cloud, en particulier les applications basées sur des architectures de microservices. En ce sens, il vaut mieux chercher à respecter l’esprit des 12-factors que chacune de ses règles à la lettre. Et pour ça, notre bon sens est notre meilleur outil.
[1] : https://docs.djangoproject.com/en/4.2/topics/settings/
[2] : https://12factor.net
[3] : Lire : https://12factor.net/config
[4] : J'utilisais par le passé le terme de secret, utilisé dans le livre Two Scoops of Django, mais les properties
ne sont pas toujours des secrets
[5] : Sentry est une solution de monitoring et de logging qui est utilisé pour capturer les erreurs en production
[6] : À noter que la réciproque est vrai : si votre application nécessite un très grand nombre de variables d’environnement pour fonctionner, il est possible que cela la complexifie la compréhension des réglages.
2022 - tominardi.fr