/ Docker

Déployer une stack Django avec Docker-Compose

Dans ce tutoriel, nous verrons comment déployer une pile complète (application web Django, avec PostgreSQL et Redis) en utilisant Docker Compose.

Tout d'abord, assurez-vous que vous avez une configuration docker prête, ou suivez cette documentation, en fonction de votre distribution, ici nous supposerons que l'hôte et la machine de développement est un Debian 8.

Nous allons devoir utiliserDocker Compose.

Nous allons déployer un total de 5 containeurs dockers :

1 Front, that contains the application code, if you don’t know Django, this will give you a little introduction to this awesome web framework.
1 Reverse proxy, that will handle the static assets and forward the dynamic ones to the Django WSGI process.
1 PostgreSQL database
1 Redis database
1 Data instance, that will contain the PostgreSQL data files, so we can freely rebuild and upgrade the PostgreSQL instance without impact on the data.

Voici un schéma de la plate-forme finale docker:

image--7-

docker_compose

Nous allons également créer une sauvegarde nocturne automatique de la base de données.

Pour commencer, nous créons un nouveau dépôt git qui contiendra tout (Juste pour la facilité, ce n'est pas obligatoire), plaçons-nous dans les répertoires suivants:

  • nginx: qui contiendra le nginx Dockerfile et la définition de vhost
  • postgres: qui contiendra le script d'initialisation de PostgreSQL,
  • web: Qui contiendra notre code d'application et le Dockerfile pour le front
  • scripts: Cela contiendra tous nos scripts (Le script de sauvegarde ici)

Les bases

Tout d'abord, nous allons créer un fichier env pour le répertoire racine du projet qui contiendra les variables d'environnement partagées par de nombreux systèmes et scripts:

DBNAME=myprojectweb
DBUSER=myprojectweb
DBPASS=shoov3Phezaimahsh7eb2Tii4ohkah8k
DBSERVICE=postgres
DBPORT=5432

Ensuite, nous créons le fichier docker-compose.yml avec le contenu suivant, Je vais vous expliquer chaque définition d'hôte:

*web:
restart: always
build: ./web/
expose:
- "8000"
links:
- postgres:postgres
- redis:redis
envfile: env
volumes:
- ./web:/data/web
command: /usr/bin/gunicorn mydjango.wsgi:application -w 2 -b :8000 *

  • Restart: ce conteneur devrait toujours être en place, et il redémarrera s'il plante
  • Build: Nous devons construire cette image en utilisant un Dockerfile avant de l'exécuter, ceci spécifie le répertoire où se trouve le Dockerfile
  • Expose: Nous exposons le port 8080 aux machines liées (qui seront utilisées par le conteneur NGINX)
  • Links: Nous devons avoir accès à l'instance postgres en utilisant le nom postgres (Ceci crée une entrée postgres dans les fichiers / etc / hosts qui pointent vers l'adresse IP de l'instance postgres), idem pour les redis.
  • Env_file: Ce conteneur chargera toutes les variables d'environnement du fichier env.
  • Volumes: nous spécifions les différents points de montage que nous voulons sur cette instance
  • Commande: quelle commande est exécutée lors du démarrage du conteneur? Ici, nous commençons le processus wsgi.

*nginx:
restart: always
build: ./nginx/
ports:
- "80:80"
volumesfrom:
- web
links:
- web:web

postgres:
restart: always
image: postgres:latest
volumesfrom:
- data
volumes:
- ./postgres/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
- ./backups/postgresql:/backup
envfile:
- env
expose:
- "5432"
*

Ici, au lieu de l'option de construction, nous avons l'option image qui cible une image existante sur le registre Docker, nous utilisons aussi volumes_from pour charger tous les volumes d'un autre conteneur.

Nous l'utilisons sur le conteneur Nginx pour charger le répertoire statique de l'application et le servir, et sur le conteneur PostgreSQL pour charger l'espace table persistant qui se trouve dans le conteneur de données.

*redis:
restart: always
image: redis:latest
expose:
- "6379"

data:
restart: always
image: alpine
volumes:
- /var/lib/postgresql
command: "true"*

• Nous créons un conteneur Redis standard, en utilisant la dernière version de l'image officielle et en exposant le port Redis.
• Sur le conteneur de données, nous utilisons la commande true (Cela ne fait rien et ne faisons que maintenir le conteneur en cours d'exécution) en tant que commande car nous voulons simplement que ce conteneur contienne l'espace table PostgreSQL.
• L'utilisation d'un conteneur de données est la méthode recommandée lorsque nous voulons gérer la persistance des données, en utilisant ceci, nous ne risquons aucune suppression accidentelle lors, par exemple, d'une mise à niveau du conteneur PostgreSQL (qui supprimera toutes les données de son conteneur)

Le container PostgreSQL

Sur le conteneur de données, nous utilisons la commande true as car nous voulons simplement que ce conteneur contienne l'espace table PostgreSQL.

Nous allons d'abord configurer le conteneur PostgreSQL pour qu'il s'initialise. L'image Postgres charge par défaut tous les scripts du répertoire /docker-entrypoint-initdb.d, créons un script simple qui créera un utilisateur et une base de données en utilisant les informations du fichier env:

*> cat postgres/docker-entrypoint-initdb.d/myprojectweb.sh

!/bin/env bash

psql -U postgres -c "CREATE USER $DBUSER PASSWORD '$DBPASS'"
psql -U postgres -c "CREATE DATABASE $DBNAME OWNER $DBUSER"

chmod a+rx postgres/docker-entrypoint-initdb.d/myprojectweb.sh*

Le containeur reverse-proxy

Nous allons configurer le conteneur nginx, la manière recommandée lors de l'utilisation de ce tutum / nginx est d'utiliser un Dockerfile, nous allons en créer un simple:

*> cat nginx/Dockerfile
FROM tutum/nginx

RUN rm /etc/nginx/sites-enabled/default
ADD sites-enabled/ /etc/nginx/sites-enabled*

Et nous avons juste à créer un fichier dans sites-enabled / pour servir les fichiers statiques et transmettre le reste à l'application:

cat nginx/sites-enabled/django
server {

listen 80;
server_name not.configured.example.com;
charset utf-8;

location /static {
    alias /data/web/mydjango/static;
}

location / {
    proxy_pass http://web:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

Le containeur de l'application web

Le plus simple est d'utiliser une image Python officielle basée sur Debian, mais l'image obtenue sera assez lourde (la mienne était comme 900MB).

Nous voulons créer une image frontale très légère afin que nous puissions la mettre à jour facilement et rapidement lorsque nous faisons du changement de code et nous voulons réduire au maximum la surface d'attaque de l'extérieur. Pour cela, nous fonderons notre image sur Alpine Linux qui est spécialisée dans ce domaine et très commune dans le monde Docker.

Ensuite, créons notre custom Alpine pour l'application Django, nous allons d'abord lancer une session interactive pour créer le projet puis nous créerons le Dockerfile.

Mais d'abord, remplissez le fichier web / requirements.txt avec tous les modules Python dont nous avons besoin:

> cat web/requirements.txt
Django1.8.5
redis
2.10.3
django-redis4.3.0
gunicorn
19.3.0
psycopg2==2.6

Ensuite on peut démarrer sur Alpine avec :

> docker run -ti --rm -v /root/myproject/web/:/data/web alpine:latest sh
/ # cd data/web/
/data/web # apk add --update python3 python3-dev postgresql-client postgresql-dev build-base gettext vim
/data/web # pip3 install --upgrade pip
/data/web # pip3 install -r requirements.txt
/data/web # django-admin startproject mydjango
/data/web # mkdir mydjango/static

Nous allons créer une instance pour créer notre application Django. Notez que nous faisons cela pour ne pas avoir à installer Python et ses dépendances sur notre système hôte. Nous allons démarrer une instance Alpine et interactivement:

> docker run -ti --rm -v /root/myproject/web/:/data/web alpine:latest sh
/ # cd data/web/
/data/web # apk add --update python3 python3-dev postgresql-client postgresql-dev build-base gettext vim
/data/web # pip3 install --upgrade pip
/data/web # pip3 install -r requirements.txt
/data/web # django-admin startproject mydjango
/data/web # mkdir mydjango/static

Remplissons le fichier de configuration (mydjango / settings.py) avec des paramètres utilisables avec les informations fournies par Docker dans les conteneurs, supprimons tout le contenu entre la partie "DATABASE" et "INTERNATIONALISATION" et le remplaçons par ceci:

if 'DB_NAME' in os.environ:
# Running the Docker image
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'HOST': os.environ['DB_SERVICE'],
'PORT': os.environ['DB_PORT']
}
}
else:
# Building the Docker image
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://redis:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}

SESSIONENGINE = "django.contrib.sessions.backends.cache"
SESSIONCACHEALIAS = "default"

Une fois cela fait, quittez simplement le conteneur avec exit (l'indicateur -rm lors de la création de l'instance le détruira lors de son départ, l'application est enregistrée dans le volume monté)

Ici, nous chargeons la base de données Docker si nous trouvons la variable d'environnement fournie par le fichier env, et utilisons toutes ces informations pour se connecter, sinon, parce que nous construisons l'image, nous ne voulons pas bloquer certaines commandes.

Pour le conteneur redis, nous visons simplement le DNS redis qui sera présent une fois déployé en utilisant docker-composer.

Maintenant nous avons notre petite application django, mettons tout ce dont nous avons besoin dans le Dockerfile du conteneur web:

FROM alpine

Initialize

RUN mkdir -p /data/web
WORKDIR /data/web
COPY requirements.txt /data/web/

Setup

RUN apk update
RUN apk upgrade
RUN apk add --update python3 python3-dev postgresql-client postgresql-dev build-base gettext
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt

Clean

RUN apk del -r python3-dev postgresql

Prepare

COPY . /data/web/
RUN mkdir -p mydjango/static/admin

Comme vous le voyez, nous nettoyons aussi les paquets dont nous n'aurons pas besoin après avoir installé les modules Python (pour compiler le module PostgreSQL par exemple).

Let the magic happen

  • Prenez un café (en fonction de votre vitesse de connexion) et construisez le bazar avec :

docker-compose build
docker-compose up -d

Ensuite, vous pouvez vérifier l'état de vos containeurs avec :

docker-compose ps
Name Command State Ports


myproject_data_1 true Up
myproject_nginx_1 /usr/sbin/nginx Up 0.0.0.0:80->80/tcp
myproject_postgres_1 /docker-entrypoint.sh postgres Up 5432/tcp
myproject_redis_1 /entrypoint.sh redis-server Up 6379/tcp
myproject_web_1 /usr/bin/gunicorn mydjango ... Up 8000/tcp

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d7162329302d myproject_nginx "/usr/sbin/nginx" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp myproject_nginx_1
402c2ca47789 myproject_web "/usr/bin/gunicorn my" 2 minutes ago Up 2 minutes 8000/tcp myproject_web_1
2c92e1fa0021 postgres:latest "/docker-entrypoint.s" 8 minutes ago Up 2 minutes 5432/tcp myproject_postgres_1
ad58f3138339 alpine "true" 9 minutes ago Restarting (0) 58 seconds ago myproject_data_1
29ece917fcbb redis:latest "/entrypoint.sh redis" 9 minutes ago Up 2 minutes 6379/tcp myproject_redis_1

Le "redémarrage" sur le conteneur de données est normal car il n'a pas de processus persistant.

Si tout va bien, allez sur votre IP publique et vous devriez voir la page par défaut de Django (Cela ne devrait fonctionner que si elle peut se connecter avec succès à la base de données et charger le moteur de cache):

Capture-d-e-cran-2017-11-26-a--10.56.43

Après avoir modifié les élèments suivants dans le fichier settings.py situé au chemin suivant :

/site/web/mydjango/mydjango

  • DEBUG = True : si votre application rencontre une erreur, le navigateur n'affichera pas une page blanche mais des informations sur le bug rencontré.
  • LANGUAGE_CODE = "en-us" : La langue utilisée dans le projet, notamment dans l'interface d'administration. Remplacez-la dès maintenant par fr.
  • TIME_ZONE = 'UTC' : le fuseau horaire que doit suivre votre projet. Remplacez 'UTC' par 'Europe/Paris'.

Une fois ces changements faits, un coup de docker-compose restart à la racine du projet (site), et voici le résultat :

Capture-d-e-cran-2017-11-26-a--22.38.16