Comment construire une application Django et Gunicorn avec Docker

introduction

Django est un puissant framework Web qui peut vous aider à lancer rapidement votre application Python. Il inclut plusieurs fonctionnalités pratiques, telles qu’un object-relational mapper, l’authentification des utilisateurs et une interface administrative personnalisable pour votre application. Il inclut également un cadre caching et encourage la conception d’applications propres via son https://docs.djangoproject.com/en/2.1/topics/http. / urls / [URL Dispatcher] et Template system.

Dans ce didacticiel, vous apprendrez à créer une application Django Polls évolutive et portable avec des conteneurs Docker. Prête à l’emploi, une application Django nécessite plusieurs modifications pour s’exécuter efficacement à l’intérieur des conteneurs, comme la consignation dans des flux de sortie standard et la configuration à l’aide de variables d’environnement transmises au conteneur. En outre, le déchargement d’actifs statiques tels que les feuilles de style JavaScript et CSS dans le stockage d’objets vous permet de rationaliser et de centraliser la gestion de ces fichiers dans un environnement contenant plusieurs conteneurs.

Vous allez implémenter ces modifications inspirées de la méthodologie Twelve-Factor pour la création d’applications Web évolutives et natives sur le cloud - sur un exemple de Django https://github.com/do-community/ Application Django-polls [Polls]. Ensuite, vous construirez l’image de l’application et exécuterez l’application conteneurisée avec Docker.

À la fin de ce tutoriel, vous aurez conteneurisé la configuration dans https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed -databases-and-spaces [Comment configurer une application Django évolutive]. Dans les tutoriels suivants de cette série, vous apprendrez à utiliser Docker Compose pour associer le conteneur Django à un proxy inverse Nginx et à déployer cette architecture sur un cluster Kubernetes.

Il est vivement recommandé de suivre le didacticiel pour comprendre les modifications apportées à l’application, mais si vous souhaitez passer au suivant, vous pouvez obtenir le code modifié à l’adresse https://github.com/do-community/. django-polls / tree / polls-docker [branche de polls-docker] du référentiel GitHub de l’application Polls.

Conditions préalables

Pour suivre ce tutoriel, vous aurez besoin de:

Étape 1 - Création de la base de données PostgreSQL et de l’utilisateur

Pour commencer, nous allons nous connecter au serveur PostgreSQL à partir de l’instance Ubuntu. Ensuite, nous allons créer une base de données PostgreSQL et un utilisateur pour l’application Django, puis configurer la base de données pour qu’elle fonctionne efficacement avec Django.

Avant de nous connecter à la base de données à partir de notre machine Ubuntu (pas du conteneur d’applications), nous devons installer le paquetage + postgresql-client + à partir des référentiels Ubuntu. Commencez par mettre à jour l’index du paquet + apt + local, puis téléchargez et installez le paquet:

sudo apt update
sudo apt install postgresql-client

Appuyez sur + Y + puis + ENTER + lorsque vous êtes invité à commencer à télécharger et à installer les paquetages.

Maintenant que vous avez installé le client, nous allons l’utiliser pour créer une base de données et un utilisateur de base de données pour notre application Django.

Pour commencer, saisissez les * Paramètres de connexion * de votre cluster en naviguant jusqu’à * Bases de données * à partir du Cloud Control Panel, puis en cliquant sur votre base de données. Vous devriez voir une boîte * Détails de la connexion * contenant quelques * paramètres de connexion * pour votre cluster. Notez ces bas.

De retour sur la ligne de commande, connectez-vous à votre cluster à l’aide de ces informations d’identification et du client PostgreSQL + psql + que nous venons d’installer:

psql -U username -h host -p port -d database -set=sslmode=require

Lorsque vous y êtes invité, entrez le mot de passe affiché à côté du nom d’utilisateur Postgres, puis appuyez sur + ENTER +.

Vous recevrez une invite PostgreSQL à partir de laquelle vous pourrez gérer la base de données.

Tout d’abord, créez une base de données pour votre projet appelée + polls +:

CREATE DATABASE polls;

Nous pouvons maintenant passer à la base de données + polls +:

\c polls;

Ensuite, créez un utilisateur de base de données pour le projet. Assurez-vous de sélectionner un mot de passe sécurisé:

CREATE USER  WITH PASSWORD '';

Nous allons maintenant modifier quelques paramètres de connexion pour l’utilisateur que nous venons de créer. Cela accélérera les opérations de la base de données, de sorte qu’il ne sera plus nécessaire de rechercher les valeurs correctes et de les définir à chaque fois qu’une connexion sera établie.

Nous définissons le codage par défaut sur + UTF-8 +, ce à quoi Django s’attend. Nous définissons également le schéma d’isolation de transaction par défaut sur «lecture validée», ce qui bloque les lectures des transactions non validées. Enfin, nous établissons le fuseau horaire. Par défaut, vos projets Django seront configurés pour utiliser + UTC. Toutes ces recommandations proviennent de du projet Django lui-même.

Entrez les commandes suivantes à l’invite de PostgreSQL:

ALTER ROLE  SET client_encoding TO 'utf8';
ALTER ROLE  SET default_transaction_isolation TO 'read committed';
ALTER ROLE  SET timezone TO 'UTC';

Maintenant, nous pouvons donner à notre nouvel utilisateur un accès pour administrer notre nouvelle base de données:

GRANT ALL PRIVILEGES ON DATABASE polls TO ;

Lorsque vous avez terminé, quittez l’invite de PostgreSQL en tapant:

\q

Une application Django, correctement configurée, peut désormais se connecter à cette base de données et la gérer. Dans l’étape suivante, nous clonerons le code de l’application Polls à partir de GitHub et définirons explicitement ses dépendances de package Python.

Étape 2 - Clonage du référentiel d’applications et déclaration des dépendances

Pour commencer le processus de conteneurisation de notre application Django Polls, nous allons d’abord cloner le référentiel django-polls, qui contient le code complet du https: // www.djangoproject.com/[Django] du projet tutorial de l’application Sondages.

Connectez-vous à votre serveur, créez un répertoire nommé + polls-project + et utilisez + git + pour cloner le repo + django-polls + depuis GitHub:

mkdir polls-project
cd polls-project
git clone https://github.com/do-community/django-polls.git

Accédez au répertoire + django-polls + et répertoriez le contenu du référentiel:

cd django-polls
ls
OutputLICENSE  README.md  manage.py  mysite  polls  templates

Vous devriez voir les objets suivants:

  • + manage.py +: Le principal utilitaire de ligne de commande utilisé pour manipuler l’application.

  • + polls +: contient le code de l’application + polls +.

  • + mysite +: contient le code et les paramètres de la portée du projet Django.

  • + templates +: contient les fichiers de modèles personnalisés pour l’interface d’administration.

Pour en savoir plus sur la structure et les fichiers du projet, consultez la page Creating a Project de la documentation officielle de Django.

Dans ce répertoire, nous allons également créer un fichier nommé + Requirements.txt + qui contiendra les dépendances Python de l’application Django.

Ouvrez un fichier nommé + Requirements.txt + dans l’éditeur de votre choix et collez-le dans les dépendances Python suivantes:

polls-project / django-polls / Requirements.txt

boto3==1.9.252
botocore==1.12.252
Django==2.2.6
django-storages==1.7.2
docutils==0.15.2
gunicorn==19.9.0
jmespath==0.9.4
psycopg2==2.8.3
python-dateutil==2.8.0
pytz==2019.3
s3transfer==0.2.1
six==1.12.0
sqlparse==0.3.0
urllib3==1.25.6

Ici, nous installons Django, le plugin + django-storages + pour le déchargement d’actifs statiques vers le stockage d’objets, le serveur WSGI + gunicorn +, l’adaptateur PostgreSQL + psycopg2 +, ainsi que des packages de dépendances supplémentaires. Notez que nous listons et versions explicitement tous les packages Python requis par notre application.

Enregistrez et fermez le fichier.

Maintenant que nous avons cloné l’application et défini ses dépendances, nous pouvons maintenant la modifier pour en assurer la portabilité.

Étape 3 - Rendre Django configurable par les variables d’environnement

L’une des recommandations les plus importantes de la méthodologie l’application à douze facteurs consiste à extraire la configuration codée en dur de la base de code de votre application. Cela vous permet de modifier facilement le comportement de votre application au moment de l’exécution en modifiant les variables d’environnement. Docker et Kubernetes suggèrent tous deux cette méthode de configuration des conteneurs. Nous allons donc adapter le fichier de paramètres de notre application pour utiliser ce modèle.

Le fichier de paramètres principal de notre projet Django (+ django-polls / mysite / settings.py +) est un module Python qui utilise des structures de données natives pour configurer l’application. Par défaut, la plupart des valeurs du fichier sont codées en dur, ce qui signifie que vous devez modifier le fichier de configuration pour modifier le comportement de l’application. Nous pouvons utiliser la fonction + getenv de Python dans le module` + os` pour configurer Django afin qu’il lise les paramètres de configuration à partir de variables d’environnement locales.

Pour ce faire, nous allons passer à + ​​settings.py + et remplacer les valeurs codées en dur de chacune des variables que nous voulons définir lors de l’exécution par un appel à + ​​os.getenv +. La fonction + os.getenv + lit la valeur d’un nom de variable d’environnement fourni. Vous pouvez éventuellement fournir un deuxième paramètre avec une valeur par défaut qui sera utilisée si la variable d’environnement n’est pas définie.

Cela nous permet de définir des variables comme ceci:

polls-project / django-polls / mysite / settings.py

. . .
SECRET_KEY =
. . .
DEBUG =
. . .

Pour + SECRET_KEY +, Django recherchera une variable d’environnement appelée + DJANGO_SECRET_KEY +. Etant donné que cela ne doit pas être codé en dur et doit être identique sur tous nos serveurs d’applications, nous voudrons le définir en externe, sans valeur de secours. Nous souhaitons que l’application échoue si nous ne le fournissons pas car cela pourrait poser problème si plusieurs copies de notre application utilisent des clés différentes.

Pour + DEBUG +, Django recherchera une variable d’environnement appelée + DJANGO_DEBUG +. Cependant, cette fois, nous avons fourni une valeur par défaut qui sera utilisée comme solution de secours si la variable n’est pas définie. Dans ce cas, nous avons choisi de définir + DEBUG + sur + False + si aucune valeur n’est fournie afin d’éviter toute fuite d’informations confidentielles, sauf si la variable est définie intentionnellement et définie sur + True + ..

Pour appliquer cette technique, ouvrez le fichier + polls-project / django-polls / mysite / settings.py + dans l’éditeur de votre choix, puis parcourez-le en externalisant les variables suivantes avec les valeurs par défaut fournies:

  • + SECRET_KEY = +

  • + DEBUG = +

  • + ALLOWED_HOSTS = +

Pour + ALLOWED_HOSTS +, nous récupérons la variable d’environnement + DJANGO_ALLOWED_HOSTS + et nous la divisons en une liste Python en utilisant +, + comme séparateur. Si la variable n’est pas définie, + ALLOWED_HOSTS + est défini sur + 127.0.0.1 +.

Une fois que vous avez modifié les variables ci-dessus, accédez à la variable + DATABASES + et configurez-la comme suit:

polls-project / django-polls / mysite / settings.py

. . .
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.{}'.format(
            os.getenv('DATABASE_ENGINE', 'sqlite3')
        ),
        'NAME': os.getenv('DATABASE_NAME', 'polls'),
        'USER': os.getenv('DATABASE_USERNAME', 'myprojectuser'),
        'PASSWORD': os.getenv('DATABASE_PASSWORD', 'password'),
        'HOST': os.getenv('DATABASE_HOST', '127.0.0.1'),
        'PORT': os.getenv('DATABASE_PORT', 5432),
        'OPTIONS': json.loads(
            os.getenv('DATABASE_OPTIONS', '{}')
        ),
    }
}
. . .

Cela définira les paramètres de base de données + default + en utilisant des variables d’environnement.

Pour + DATABASES ['default'] ['OPTIONS'] +, nous avons utilisé + json.loads + pour désérialiser un objet JSON transmis via la variable d’environnement + DATABASE_OPTIONS +. La plupart du temps, interpréter les variables d’environnement en tant que chaînes simples facilite la lecture de la traduction vers les paramètres Django. Cependant, dans ce cas, il est utile de pouvoir passer dans une structure de données arbitraire. Chaque moteur de base de données dispose d’un ensemble unique d’options valides. Par conséquent, le fait de pouvoir coder un objet JSON avec les paramètres appropriés nous offre une plus grande flexibilité, au prix d’une certaine lisibilité.

Pour utiliser la bibliothèque + json +, importez-la en haut de + settings.py +:

polls-project / django-polls / mysite / settings.py

"""
Django settings for mysite project.

Generated by 'django-admin startproject' using Django 2.1.

For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""

import os

. . .

L’autre domaine qui requiert une attention particulière est + DATABASES ['default'] ['NAME'] +. Pour la plupart des moteurs de base de données, il s’agit du nom de la base de données dans le système de gestion de base de données relationnelle. D’autre part, si vous utilisez SQLite, + NAME + est utilisé pour spécifier le fichier de base de données, assurez-vous donc de définir ce paramètre en conséquence.

Comme le fichier + settings.py + est du code Python, vous pouvez gérer les valeurs de lecture de l’environnement de différentes manières. La méthode que nous avons utilisée ici n’est qu’une technique possible pour externaliser la configuration à partir de votre base de code.

Au cours de cette étape, nous avons configuré les principales variables de paramètres Django de manière générique et portable, y compris les paramètres de base de données. Dans l’étape suivante, nous allons continuer à configurer les paramètres pour les fichiers statiques tels que les feuilles de style Javascript et CSS, que nous centraliserons et déchargerons vers un service de stockage d’objets compatible S3.

Étape 4 - Déchargement des actifs statiques

Lorsque vous exécutez plusieurs conteneurs Django dans un environnement de production, il peut s’avérer fastidieux de conserver des versions spécifiques d’actifs et de fichiers statiques sur l’ensemble du parc de conteneurs en cours d’exécution. Pour rationaliser cette architecture, nous pouvons décharger tous les éléments partagés et leur état sur un stockage externe. Au lieu d’essayer de maintenir ces éléments synchronisés sur les répliques ou d’implémenter des routines de sauvegarde et de chargement pour garantir la disponibilité locale des données, nous pouvons implémenter l’accès à ces actifs en tant que services accessibles au réseau.

Lors de la dernière étape, nous avons configuré Django pour pouvoir transmettre les paramètres de connexion à la base de données via des variables d’environnement. Dans cette étape, nous ferons la même chose pour notre service de stockage d’objets, que nous utiliserons pour stocker des actifs statiques qui seront partagés par les conteneurs Django.

Le paquet django-storages fournit des serveurs de stockage distants (y compris un stockage d’objets compatible S3) que Django peut utiliser pour décharger des fichiers. Nous allons configurer l’application Polls afin qu’elle utilise + django-storages + pour télécharger des fichiers statiques vers un espace DigitalOcean, comme indiqué à l’étape 7 de la page https://www.digitalocean.com/community/tutorials/how-to-set- up-a-scalable-app-Django-avec-digitalocean-gérées-bases-de-données-et-espaces # step-7-% E2% 80% 94-déchargement-fichiers-statiques-en-digitalocean-espaces [Comment configurer un Application Django évolutive avec bases de données et espaces gérés DigitalOcean]. Dans ce guide, nous utiliserons DigitalOcean Spaces, mais vous pouvez utiliser n’importe quel fournisseur de stockage d’objets compatible S3.

Pour commencer, nous allons apporter quelques modifications au même fichier + django-polls / mysite / settings.py que nous avons modifié aux étapes précédentes.

Commencez par ouvrir le fichier + mysite / settings.py + pour le modifier et ajouter l’application + storages + à la liste de Django de + INSTALLED_APPS +:

polls-project / django-polls / mysite / settings.py

. . .
INSTALLED_APPS = [
   . . .
   'django.contrib.staticfiles',

]
. . .

L’application + storages + est installée via + django-storages + dans le fichier + Requirements.txt + défini à l’étape 1.

Maintenant, localisez la variable + STATIC_URL + au bas du fichier et remplacez-la par le bloc suivant:

polls-project / django-polls / mysite / settings.py

. . .

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/

# Moving static assets to DigitalOcean Spaces as per:
# https://www.digitalocean.com/community/tutorials/how-to-set-up-object-storage-with-django
AWS_ACCESS_KEY_ID = os.getenv('STATIC_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('STATIC_SECRET_KEY')

AWS_STORAGE_BUCKET_NAME = os.getenv('STATIC_BUCKET_NAME')
AWS_S3_ENDPOINT_URL = os.getenv('STATIC_ENDPOINT_URL')
AWS_S3_OBJECT_PARAMETERS = {
   'CacheControl': 'max-age=86400',
}
AWS_LOCATION = 'static'
AWS_DEFAULT_ACL = 'public-read'

STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

STATIC_URL = '{}/{}/'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION)
STATIC_ROOT = 'static/'

Nous codons en dur les variables de configuration suivantes:

  • + STATICFILES_STORAGE +: Définit le backend de stockage utilisé par Django pour décharger les fichiers statiques. Ce backend + S3Boto3Storage + devrait fonctionner avec tout backend compatible S3, y compris les espaces DigitalOcean.

  • + AWS_S3_OBJECT_PARAMETERS + Définit les en-têtes de contrôle du cache sur les fichiers statiques.

  • + AWS_LOCATION +: définit un répertoire appelé + static + dans le compartiment de stockage d’objets dans lequel tous les fichiers statiques seront placés.

  • ` + AWS_DEFAULT_ACL + `: définit la liste de contrôle d’accès (ACL) pour les fichiers statiques. Le paramétrer sur `+ public-read + garantit que les fichiers sont accessibles publiquement aux utilisateurs finaux.

  • + STATIC_URL +: Spécifie l’URL de base que Django doit utiliser lors de la génération d’URL pour les fichiers statiques. Ici, nous combinons l’URL du noeud final et le sous-répertoire des fichiers statiques pour construire une URL de base pour les fichiers statiques.

  • + STATIC_ROOT +: spécifie où collecter les fichiers statiques localement avant de les copier dans le stockage d’objets.

Pour maintenir la flexibilité et la portabilité, nous avons configuré la plupart des paramètres de manière à pouvoir être configurés au moment de l’exécution à l’aide de variables d’environnement, comme auparavant. Ceux-ci inclus:

  • + AWS_ACCESS_KEY_ID +: Défini par la variable d’environnement + STATIC_ACCESS_KEY_ID +. Identifiant de la clé d’accès DigitalOcean Spaces.

  • + AWS_SECRET_ACCESS_KEY +: Défini par + STATIC_SECRET_KEY +. La clé secrète des espaces DigitalOcean.

  • + AWS_STORAGE_BUCKET_NAME +: Défini par + STATIC_BUCKET_NAME +. Le compartiment de stockage d’objets dans lequel Django téléchargera les actifs.

  • + AWS_S3_ENDPOINT_URL +: Défini par + STATIC_ENDPOINT_URL +. L’URL du noeud final utilisé pour accéder au service de stockage d’objets. Pour les espaces DigitalOcean, ce sera quelque chose comme + https: // nyc3.digitaloceanspaces.com +, en fonction de la région où se trouve votre compartiment Spaces.

Lorsque vous avez terminé de modifier + settings.py +, enregistrez et fermez le fichier.

À partir de maintenant, lorsque vous exécuterez + manage.py collectstatic + pour assembler les fichiers statiques de votre projet, Django les téléchargera dans un stockage d’objets distant. Django est également configuré pour servir des actifs statiques à partir de ce service de stockage d’objets.

À ce stade, si vous utilisez un espace DigitalOcean, vous pouvez éventuellement activer un CDN pour votre espace, ce qui accélérera la livraison des fichiers statiques de votre projet Django en les mettant en cache sur un réseau de serveurs Edge répartis géographiquement. Vous pouvez également éventuellement configurer un sous-domaine personnalisé pour votre espace. Pour en savoir plus sur les CDN, consultez la page Utilisation d’un CDN pour accélérer la diffusion de contenu statique. La configuration d’un CDN dépasse le cadre de ce tutoriel, mais les étapes à suivre sont très proches de celles décrites dans la https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app- with-digitalocean-gérées-bases-de-données-et-espaces abling-cdn-optional [Activation de CDN] de https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django -app-with-digitalocean-bases-gérées-gérées-et- #abling-cdn-optional [Comment configurer une application Django évolutive avec des bases de données et des espaces gérés DigitalOcean].

Dans la prochaine étape, nous apporterons une dernière série de modifications à + ​​settings.py + qui permettront à Django de se connecter à STDOUT et à STDERR afin que ces flux puissent être capturés par le moteur Docker et inspectés à l’aide de + docker logs + .

Étape 5 - Configuration de la journalisation

Par défaut, Django enregistre les informations sur la sortie standard et sur l’erreur standard lors de l’exécution du serveur HTTP de développement ou lorsque l’option + DEBUG + est définie sur + True +. Cependant, lorsque + DEBUG + est défini sur + False + ou lorsque vous utilisez un serveur HTTP différent, qui sont vraisemblablement vrais dans les environnements de production, Django utilise un mécanisme de journalisation différent. Au lieu de consigner tout ce qui est prioritaire + INFO + et supérieur aux flux standard, il envoie des messages de priorité + ERROR + ou + + CRITICAL + `à un compte de messagerie administratif.

Cela est logique dans de nombreuses situations, mais dans Kubernetes et les environnements conteneurisés, la connexion à la sortie standard et aux erreurs standard est vivement recommandée. Les messages de journalisation sont rassemblés dans un répertoire centralisé du système de fichiers du nœud et sont accessibles de manière interactive à l’aide des commandes + kubectl + et + + docker + `. Cette agrégation au niveau du nœud facilite la collecte de journaux en permettant aux équipes opérationnelles d’exécuter un processus sur chaque nœud pour surveiller et transférer les journaux. Pour tirer parti de cette architecture, l’application doit écrire ses journaux dans ces récepteurs standards.

Heureusement, la connexion à Django utilise le module + logging + hautement configurable de la bibliothèque standard Python. Nous pouvons donc définir un dictionnaire à transmettre à https://docs.python.org/3/library/logging.config.html#logging -config-dictschema [+ logging.config.dictConfig +] pour définir les sorties et le formatage souhaités. Pour en savoir plus sur cette technique et sur d’autres méthodes de configuration de la journalisation Django, consultez la page Django Logging, The Right Way.

Encore une fois, ouvrez + django-polls / mysite / settings.py + dans votre éditeur.

Nous allons d’abord ajouter une instruction + import + supplémentaire en haut du fichier afin de pouvoir manipuler la configuration de la journalisation:

polls-project / django-polls / mysite / settings.py

import json
import os

. . .

L’importation + logging.config + nous permet de remplacer le comportement de journalisation par défaut de Django en transmettant un dictionnaire de la nouvelle configuration de journalisation à la fonction + dictConfig +.

Maintenant, accédez au bas du fichier et collez-le dans le bloc de code de configuration de la journalisation suivant:

polls-project / django-polls / mysite / settings.py

. . .
# Logging Configuration

# Clear prev config
LOGGING_CONFIG = None

# Get loglevel from env
LOGLEVEL = os.getenv('DJANGO_LOGLEVEL', 'info').upper()

logging.config.dictConfig({
   'version': 1,
   'disable_existing_loggers': False,
   'formatters': {
       'console': {
           'format': '%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(module)s %(process)d %(thread)d %(message)s',
       },
   },
   'handlers': {
       'console': {
           'class': 'logging.StreamHandler',
           'formatter': 'console',
       },
   },
   'loggers': {
       '': {
           'level': LOGLEVEL,
           'handlers': ['console',],
       },
   },
})

Ici, nous définissons + LOGGING_CONFIG + sur + None + pour désactiver la configuration de journalisation par défaut fournie par Django. Nous définissons + LOGLEVEL + sur + INFO + par défaut, mais vérifions la variable d’environnement + DJANGO_LOGLEVEL + afin de pouvoir remplacer si nécessaire.

Enfin, nous utilisons la fonction + dictConfig + pour définir un nouveau dictionnaire de configuration à l’aide du module + logging.config +. Dans le dictionnaire, nous définissons le format du texte en utilisant + formatters +, nous définissons la sortie en configurant + handlers + et en configurant les messages qui doivent parvenir à chaque gestionnaire en utilisant + loggers +.

Il s’agit d’une configuration plutôt minimale qui vous permet de spécifier un niveau de gravité de la journalisation à l’aide d’une variable d’environnement appelée + DJANGO_LOGLEVEL +, puis de consigner tous les messages de ce niveau ou supérieurs dans des flux standard. Pour une discussion approfondie sur les mécanismes de journalisation Django, consultez la page Logging des documents officiels de Django.

Avec cette configuration, lorsque nous conteneurisons l’application, Docker expose ces journaux via la commande + docker logs +. De même, Kubernetes capturera la sortie et l’exposera à l’aide de la commande + kubectl logs +.

Ceci conclut nos modifications de code à l’application Django Polls. Dans la prochaine étape, nous allons commencer le processus de conteneurisation en écrivant le fichier Docker de l’application.

Étape 6 - Écriture du fichier de dock d’application

Dans cette étape, nous définirons l’image de conteneur qui exécutera notre application Django et le serveur Gunicorn WSGI qui la servira. Cela implique la création d’une image de conteneur en définissant l’environnement d’exécution, en installant l’application et ses dépendances, et en effectuant certaines configurations de base. Bien qu’il existe de nombreuses façons d’encapsuler une application dans une image de conteneur, les pratiques suivies à cette étape produisent une image d’application mince et rationalisée.

Choisir une image parent appropriée

La première décision importante que vous devrez prendre lors de la création d’une image de conteneur est la base sur laquelle bâtir. Les images de conteneur peuvent être construites à partir de + SCRATCH +, indiquant un système de fichiers vide, ou à partir d’une image de conteneur existante. De nombreuses images de conteneur de base différentes sont disponibles, chacune définissant un système de fichiers et fournissant un ensemble unique de packages préinstallés. Les images basées sur des distributions Linux vanilla telles que Ubuntu 18.04 fournissent un environnement d’exploitation générique, tandis que des images plus spécialisées incluent souvent des bibliothèques et des outils communs pour des langages de programmation spécifiques.

Dans la mesure du possible, il est souvent conseillé d’utiliser comme base une image de l’un des dépôts officiels Docker. Docker a vérifié que ces images étaient conformes aux meilleures pratiques et sont mises à jour régulièrement pour intégrer des correctifs de sécurité et des améliorations.

Puisque notre application est construite avec Django, une image avec un environnement Python standard fournira une base solide et inclura la plupart des outils dont nous avons besoin pour commencer. Le référentiel officiel Docker pour Python propose une large sélection d’images Python, chacune d’elles installant une version de Python et des outils communs par-dessus un système d’exploitation.

Bien que le niveau de fonctionnalité approprié dépend de votre cas d’utilisation, les images basées sur Alpine Linux constituent souvent un solide point de départ. Alpine Linux offre un environnement d’exploitation robuste, mais minimal, pour l’exécution d’applications. Son système de fichiers par défaut est très petit, mais comprend un système complet de gestion de paquets avec des référentiels assez étendus pour faciliter l’ajout de fonctionnalités.

Dans ce guide, nous utiliserons l’image Python étiquetée «+ 3.7.4-alpine3.10 » comme image parente de notre application Django. Nous spécifions le référentiel et la balise de l'image parente dans notre ` Dockerfile ` en utilisant l'instruction ` FROM +`.

Tout d’abord, sortez du répertoire + django-polls +.

cd ..

Ensuite, ouvrez un fichier nommé + Dockerfile + dans l’éditeur de votre choix. Collez dans la définition de l’image parente suivante:

polls-project / Dockerfile

FROM python:3.7.4-alpine3.10

Cela définit le point de départ de l’image Docker personnalisée que nous construisons pour exécuter notre application.

Ajout d’instructions pour configurer l’application

Une fois que vous avez choisi une image parent, vous pouvez commencer à ajouter des instructions pour installer des dépendances, copier sur nos fichiers d’application et configurer l’environnement en cours d’exécution. Ce processus reflète généralement les étapes à suivre pour configurer un serveur pour votre application, avec quelques différences essentielles pour tenir compte des abstractions de conteneur.

Après la ligne + FROM +, collez le bloc suivant de code Dockerfile:

polls-project / Dockerfile

. . .

ADD django-polls/requirements.txt /app/requirements.txt

RUN set -ex \
   && apk add --no-cache --virtual .build-deps postgresql-dev build-base \
   && python -m venv /env \
   && /env/bin/pip install --upgrade pip \
   && /env/bin/pip install --no-cache-dir -r /app/requirements.txt \
   && runDeps="$(scanelf --needed --nobanner --recursive /env \
       | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
       | sort -u \
       | xargs -r apk info --installed \
       | sort -u)" \
   && apk add --virtual rundeps $runDeps \
   && apk del .build-deps

ADD django-polls /app
WORKDIR /app

ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH

EXPOSE 8000

Examinons ces instructions pour expliquer certains des choix les moins évidents. Pour en savoir plus sur la création de fichiers Dockerfiles prêts pour la production pour les applications Django, consultez A Production. -Ready Dockerfile pour votre application Django.

Docker d’abord copiera le fichier + Requirements.txt + dans + / app / Requirements.txt + afin que les dépendances de notre application soient disponibles sur le système de fichiers de l’image. Nous allons l’utiliser pour installer tous les packages Python nécessaires à l’exécution de notre application. Nous copions le fichier de dépendances comme une étape distincte du reste de notre base de code afin que Docker puisse mettre en cache la couche d’image contenant le fichier de dépendances. À chaque fois que le fichier + Requirements.txt + ne change pas entre les versions, Docker peut alors réutiliser le calque mis en cache au lieu de le reconstruire, accélérant ainsi le processus.

Ensuite, nous avons une seule instruction + RUN + qui exécute une longue liste de commandes, chacune chaînée à l’aide de l’opérateur Linux + && +. Pour résumer, ces commandes:

  • Installez les fichiers de développement PostgreSQL et les dépendances de construction de base en utilisant le gestionnaire de paquets + apk + d’Alpine

  • Créer un environnement virtuel

  • Installez les dépendances Python listées dans + Requirements.txt avec` + pip`

  • Compiler une liste des packages nécessaires au moment de l’exécution en analysant les exigences des packages Python installés

  • Désinstallez les dépendances de construction inutiles.

Nous enchaînons les commandes au lieu de les exécuter dans une étape distincte + RUN + en raison de la manière dont Docker construit les calques d’image. Pour chaque instruction `+ ADD,` + COPY` et + RUN +, Docker crée un nouveau calque d’image par-dessus le système de fichiers existant, exécute l’instruction, puis enregistre le calque obtenu. Cela signifie que la compression des commandes dans les instructions + RUN + entraînera moins de couches d’image.

Une fois qu’un élément a été écrit dans un calque d’image, il ne peut pas être supprimé d’un autre calque pour réduire la taille de l’image. Si nous installons des dépendances de construction mais que nous souhaitons les supprimer une fois l’application configurée, nous devons le faire dans le même ordre afin de réduire la taille de l’image. Dans cette commande + RUN +, nous installons des dépendances de construction, nous les utilisons pour construire les packages de l’application, puis nous les supprimons à l’aide de + apk del +.

Après l’instruction + RUN +, nous utilisons + ADD pour copier dans le code de l’application et` + WORKDIR` pour définir le répertoire de travail de l’image dans notre répertoire de code.

Ensuite, nous utilisons l’instruction + ENV + pour définir deux variables d’environnement qui seront disponibles dans les conteneurs générés à partir de notre image. Le premier définit + VIRTUALENV sur` + / env + et la seconde instruction modifie la variable + PATH + pour inclure le répertoire + / env / bin + . Ces deux lignes émulent les résultats de la recherche du script `+ / env / bin / activate +, qui est la méthode traditionnelle d’activation d’un environnement virtuel.

Enfin, nous utilisons + EXPOSE + pour informer Docker que le conteneur écoutera sur le port + 8000 + lors de l’exécution.

À ce stade, le + Dockerfile + est presque terminé. Nous avons juste besoin de définir la commande par défaut qui sera exécutée lorsque nous démarrons des conteneurs en utilisant l’image.

Définir la commande par défaut

La commande par défaut d’une image Docker détermine ce qui se produit lors du démarrage d’un conteneur sans fournir explicitement une commande à exécuter. Les instructions + ENTRYPOINT + et + + CMD + peuvent être utilisées indépendamment ou en tandem pour définir une commande par défaut dans le fichier + Dockerfile +.

Quand + ENTRYPOINT + et + CMD + sont définis, le + ENTRYPOINT + définit l’exécutable qui sera exécuté par le conteneur, et le + CMD + représente la liste d’arguments par défaut pour cette commande. Les utilisateurs peuvent remplacer la liste d’arguments par défaut en ajoutant d’autres arguments sur la ligne de commande: + docker run <image> <arguments> +. Dans ce format, les utilisateurs ne pourront pas facilement remplacer la commande + ENTRYPOINT +, aussi la commande + ENTRYPOINT + est-elle souvent définie sur un script qui configurera l’environnement et effectuera différentes actions en fonction de la liste d’arguments qu’il reçoit.

Lorsqu’il est utilisé seul, + ENTRYPOINT + configure l’exécutable du conteneur, mais ne définit pas de liste d’arguments par défaut. Si seulement + CMD + est défini, il sera interprété comme la liste de commandes et d’arguments par défaut, qui peut être remplacée au moment de l’exécution.

Dans notre image, nous voulons que le conteneur exécute votre application par défaut en utilisant le serveur d’applications + gunicorn. La liste d’arguments que nous transmettons à + ​​gunicorn + n’a pas besoin d’être configurable à l’exécution, mais nous voulons pouvoir exécuter facilement d’autres commandes si nécessaire pour déboguer ou effectuer des tâches de gestion (comme la collecte d’actifs statiques ou l’initialisation de la base de données). Compte tenu de ces exigences, il est logique que nous utilisions + CMD + pour définir une commande par défaut sans + ENTRYPOINT +.

L’instruction + CMD + peut être définie en utilisant l’un des formats suivants:

  • + CMD [" "," ",. . . , ""] + `: Le format de la liste d’arguments (utilisé pour définir la liste d’arguments par défaut pour un + ENTRYPOINT + `)

  • + CMD [" "," "," ",. . . , ""] + `: Le format + exec + `

  • `+ CMD" "" ". . . "" + `: Le format du shell

Le premier format ne répertorie que les arguments et est utilisé en conjonction avec un + ENTRYPOINT +. Les deux autres formats spécifient les commandes et leurs arguments, avec quelques différences essentielles. Le format + exec +, recommandé, exécute directement la commande en transmettant la liste des arguments sans traitement shell. Le format shell, quant à lui, passe toute la liste à + ​​sh -c +. Cela est nécessaire si, par exemple, vous devez substituer la valeur d’une variable d’environnement dans une commande, mais que vous êtes généralement considéré comme moins prévisible.

Pour nos besoins, la dernière instruction dans notre + Dockerfile + ressemble à ceci:

polls-project / Dockerfile

. . .
CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi:application"]

Par défaut, les conteneurs utilisant cette image exécuteront + gunicorn + lié au port localhost + 8000 + avec 3 travailleurs, et exécuteront la fonction + application + dans le fichier + wsgi.py + qui se trouve dans le dossier + mysite + annuaire. Vous pouvez éventuellement fournir une commande au moment de l’exécution pour exécuter un processus différent au lieu de + gunicorn +.

À ce stade, vous pouvez utiliser + docker build pour créer votre image d’application et` + docker run` pour exécuter le conteneur sur votre ordinateur.

Construire l’image de Docker

Par défaut, la commande + docker build recherche un` + Dockerfile` dans le répertoire en cours pour trouver ses instructions de construction. Il envoie également au démon Docker le «contexte» de construction, la hiérarchie du système de fichiers local qui devrait être disponible pendant le processus de construction. Souvent, le répertoire en cours est défini comme contexte de construction.

Après avoir accédé au répertoire contenant votre + Dockerfile, exécutez` + docker build`, en passant une image et un nom de balise avec l’indicateur + -t +, et utilisez le répertoire actuel comme contexte de construction. Ici, nous nommons l’image + django-polls + et nous l’ajoutons à la version + v0 +:

docker build -t : .

La commande passera le répertoire + Dockerfile + et le répertoire en cours en tant que contexte de construction au démon Docker. Le démon construira votre image en créant une série de couches d’images pendant le traitement des instructions + Dockerfile +.

Lorsque + docker build est terminé, vous devriez voir le résultat suivant:

OutputSuccessfully built
Successfully tagged django-polls:v0

Une fois l’image créée, vous pouvez exécuter le conteneur d’applications à l’aide de + docker run +. Cependant, la commande + run + échouera probablement ici, car nous n’avons toujours pas configuré l’environnement en cours d’exécution du conteneur. Les variables externalisées telles que + SECRET_KEY + et les paramètres de base de données de + settings.py + seront soit vierges, soit définis sur des valeurs par défaut.

Dans la dernière étape, nous allons configurer l’environnement en cours du conteneur à l’aide d’un fichier de variable d’environnement. Ensuite, nous allons créer le schéma de base de données, générer et télécharger les fichiers statiques de l’application sur le stockage d’objets et enfin tester l’application.

Étape 7 - Configuration de l’environnement d’exécution et test de l’application

Docker fournit les différentes méthodes pour définir des variables d’environnement à l’intérieur du récipient. Comme nous devons définir toutes les variables que nous avons externalisées à l’étape 1, nous utiliserons la méthode + - env-file +, qui nous permet de transmettre un fichier contenant une liste de variables d’environnement et leurs valeurs.

Créez un fichier nommé + env + dans le répertoire + polls-project + et collez-le dans la liste de variables suivante:

sondages-projet / env

DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=polls
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=https://.digitaloceanspaces.com
DJANGO_LOGLEVEL=info

Remplacez les valeurs suivantes dans ce fichier:

  • + DJANGO_SECRET_KEY +: définissez cette valeur sur une valeur unique et imprévisible, comme indiqué dans les documents Django. Une méthode pour générer cette clé est fournie dans https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces # step-5-% E2% 80% 94-ajuster les paramètres de l’application [Ajuster les paramètres de l’application] de la page https://www.digitalocean.com/community/tutorials/how-to-set-up-a -scalable-django-app-with-digitalocean-géré-bases-de-données-et-espaces # step-5-% E2% 80% 94-ajustement-the-app-settings [Scalable Django App].

  • + DJANGO_ALLOWED_HOSTS +: Définissez ceci sur l’adresse IP de votre serveur Ubuntu. À des fins de test, vous pouvez également le définir sur + * +, un caractère générique qui correspondra à tous les hôtes. Veillez à définir correctement cette valeur lors de l’exécution de Django dans un environnement de production.

  • + DATABASE_USERNAME +: Définissez ceci sur l’utilisateur de base de données créé à l’étape précédente.

  • + DATABASE_PASSWORD +: définissez ceci sur le mot de passe utilisateur créé à l’étape précédente.

  • + DATABASE_HOST: définissez ceci sur le nom d’hôte de votre base de données.

  • + DATABASE_PORT +: Définissez ceci sur le port de votre base de données.

  • + STATIC_ACCESS_KEY_ID +: Définissez ceci sur la clé d’accès de votre espace.

  • + STATIC_SECRET_KEY +: définissez ceci sur la clé d’accès de votre espace, Secret.

  • + STATIC_BUCKET_NAME +: Définissez ceci sur votre nom d’espace.

  • + STATIC_ENDPOINT_URL +: définissez cette option sur l’URL de noeud final Space appropriée.

Lorsque vous exécutez Django en production, assurez-vous de définir + DEBUG + sur + False + et d’ajuster le niveau de journalisation en fonction de la verbosité souhaitée.

Enregistrez et fermez le fichier.

Nous allons maintenant utiliser + docker run + pour remplacer le paramètre + CMD + dans le fichier Dockerfile et créer le schéma de base de données à l’aide des commandes + manage.py makemigrations + et + manage.py migrate + `:

docker run --env-file env django-polls:v0 sh -c "python manage.py makemigrations && python manage.py migrate"

Ici, nous lançons l’image du conteneur + django-polls: v0 +, transmettons le fichier de variable d’environnement que nous venons de créer et remplaçons la commande Dockerfile par `+ sh -c" python manage.py makemigrations && python manage.py migrate " + `, ce qui créera le schéma de base de données défini par le code de l’application. Après avoir exécuté la commande, vous devriez voir:

OutputNo changes detected
Operations to perform:
 Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
 Applying contenttypes.0001_initial... OK
 Applying auth.0001_initial... OK
 Applying admin.0001_initial... OK
 Applying admin.0002_logentry_remove_auto_add... OK
 Applying admin.0003_logentry_add_action_flag_choices... OK
 Applying contenttypes.0002_remove_content_type_name... OK
 Applying auth.0002_alter_permission_name_max_length... OK
 Applying auth.0003_alter_user_email_max_length... OK
 Applying auth.0004_alter_user_username_opts... OK
 Applying auth.0005_alter_user_last_login_null... OK
 Applying auth.0006_require_contenttypes_0002... OK
 Applying auth.0007_alter_validators_add_error_messages... OK
 Applying auth.0008_alter_user_username_max_length... OK
 Applying auth.0009_alter_user_last_name_max_length... OK
 Applying auth.0010_alter_group_name_max_length... OK
 Applying auth.0011_update_proxy_permissions... OK
 Applying polls.0001_initial... OK
 Applying sessions.0001_initial... OK

Cela indique que le schéma de base de données a été créé avec succès.

Ensuite, nous allons exécuter une autre instance du conteneur d’applications et utiliser un shell interactif à l’intérieur pour créer un utilisateur administratif pour le projet Django.

docker run -i -t --env-file env django-polls:v0 sh

Cela vous fournira une invite du shell à l’intérieur du conteneur en cours d’exécution que vous pouvez utiliser pour créer l’utilisateur Django:

python manage.py createsuperuser

Entrez un nom d’utilisateur, une adresse e-mail et un mot de passe pour votre utilisateur. Après avoir créé l’utilisateur, appuyez sur les touches + CTRL + D + pour quitter le conteneur et le tuer.

Enfin, nous allons générer les fichiers statiques de l’application et les télécharger dans DigitalOcean Space en utilisant + collectstatic +:

docker run --env-file env django-polls:v0 sh -c "python manage.py collectstatic --noinput"
Output
121 static files copied.

Nous pouvons maintenant lancer l’application:

docker run --env-file env -p 80:8000 django-polls:v0
Output[2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync
[2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7
[2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8
[2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

Ici, nous exécutons la commande par défaut définie dans le fichier Docker, + gunicorn --bind: 8000 --workers 3 mysite.wsgi: application +, et exposons le port de conteneur + 8000 + de sorte que le port + 80 + du Le serveur Ubuntu est mappé sur le port + 8000 + du conteneur + django-polls: v0 +.

Vous devriez maintenant pouvoir naviguer vers l’application + polls + en utilisant votre navigateur web en tapant + http: // + dans la barre d’URL. Comme il n’ya pas de route définie pour le chemin + / +, vous recevrez probablement une erreur 404 Page Not Found, qui est attendue.

Accédez à + ​​http: /// polls + pour voir l’interface de l’application Polls:

image: https: //assets.digitalocean.com/articles/scalable_django/polls_app.png [Interface de l’application Polls]

Pour vérifier l’interface d’administration, visitez + http: /// admin +. La fenêtre d’authentification de l’administrateur de l’application Polls devrait s’afficher:

image: https: //assets.digitalocean.com/articles/scalable_django/polls_admin.png [Page d’authentification d’administrateur]

Entrez le nom d’utilisateur administratif et le mot de passe que vous avez créés avec la commande + createuperuser +.

Après authentification, vous pouvez accéder à l’interface administrative de l’application Polls:

image: https: //assets.digitalocean.com/articles/scalable_django/polls_admin_main.png [Interface principale de l’administrateur de sondages]

Notez que les actifs statiques pour les applications + admin + et + polls + sont livrés directement à partir du stockage d’objets. Pour le confirmer, consultez https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#testing-spaces. -static-file-delivery [Livraison de fichiers statiques pour les espaces testés].

Lorsque vous avez terminé votre exploration, cliquez sur + CTRL-C + dans la fenêtre du terminal exécutant le conteneur Docker pour tuer le conteneur.

Conclusion

Dans ce didacticiel, vous avez adapté une application Web Django pour fonctionner efficacement dans un environnement en nuage basé sur des conteneurs. Vous avez ensuite écrit un fichier Docker minimal pour l’image du conteneur, l’avez construit localement et l’avez exécuté à l’aide de Docker Engine. Vous pouvez voir une diff des modifications que vous avez implémentées dans la https://github.com/do-community/django-polls/tree / polls-docker [branche polls-docker] du référentiel GitHub de l’application Polls. Cette branche contient toutes les modifications décrites dans ce tutoriel.

À partir de là, vous pouvez associer le conteneur Django / Gunicorn à un conteneur de proxy inverse Nginx pour gérer et router les demandes HTTP entrantes, et à un conteneur Certbot pour obtenir des certificats TLS. Vous pouvez gérer cette architecture multi-conteneurs à l’aide de Docker Compose; cela sera décrit dans un prochain tutoriel.

Notez qu’en l’état, cette configuration n’est pas prête pour la production, car vous devriez toujours exécuter Gunicorn derrière un proxy HTTP pour mettre en mémoire tampon les clients lents. Sinon, votre application Web Django sera vulnérable aux attaques par déni de service. Nous avons également choisi 3 comme nombre arbitraire de travailleurs Gunicorn dans ce tutoriel. En production, vous devez définir le nombre de travailleurs et de threads à l’aide de critères de performances.

Dans cette architecture, nous avons choisi de décharger les actifs statiques du stockage d’objet afin que les conteneurs ne soient pas obligés de regrouper une version de ces actifs et de les servir à l’aide de Nginx, qui peut devenir difficile à gérer dans des environnements de cluster à conteneurs multiples tels que Kubernetes. . Selon votre cas d’utilisation, il se peut que cette conception ne soit pas efficace. Vous devez donc adapter les étapes de ce didacticiel en conséquence.

Enfin, maintenant que l’application Django Polls est entièrement conteneurisée, vous pouvez transférer l’image dans un registre de conteneurs tel que Dockerhub et la mettre à la disposition de tout système sur lequel Docker est disponible: Ubuntu serveurs, machines virtuelles et clusters de conteneurs tels que Kubernetes.