• 4 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 08/11/2019

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Introduction du cours

Vous programmez en Python et vous avez créé une application Django que vous avez déployée sur Heroku. Mais ça commence à revenir un peu cher ! Ou bien vous aimeriez totalement maîtriser votre environnement de production et ne pas passer par les scripts d'exécution de votre plateforme en tant que service ?

Découvrez dans ce cours comment déployer une application par vous-même en configurant un serveur virtuel de A à Z en hébergement mutualisé. Nous verrons en détail quels logiciels installer sur Ubuntu, comment configurer Nginx, Gunicorn et lancer des processus sous supervision. Vous verrez également comment faire de l'intégration continue. Allez, vous êtes prêts à déployer comme les pros ?

C’est parti !

Lancez Django sur un serveur distant

Découvrez l'administration système

Bienvenue dans ce cours !

Vous allez y découvrir l'univers fabuleux des Ops, ou Administrateurs Systèmes en français (les fameux SysAdmins) ! Ils sont responsables du bon fonctionnement des sites sur Internet. C'est une grande mission !

Qu'est-ce qu'un SysAdmin ?

Un SysAdmin prend le code d'un développeur et s'occupe de tout mettre en oeuvre pour qu'il fonctionne sur un serveur. Le développeur donne un projet terminé et le SysAdmin s'occupe de le mettre en production.

C'est pour cela que le SysAdmin est considéré comme le pompier ! Une erreur serveur ? On appelle le SysAdmin. Le site est trop lent ? Le SysAdmin. Les utilisateurs n'arrivent plus à télécharger les fichiers ? ... Vous avez compris l'idée.

Vous pensez peut-être que ce sont des êtres détestant la lumière et vivant dans une cave ? Détrompez-vous ! Leur métier est passionnant et vous découvrirez pourquoi dans ce cours !

Le Cloud, ce nuage paradisiaque

Pourquoi perdre du temps à configurer des serveurs lorsque l'on peut passer par Heroku ?

Bonne question, jeune padawan !

En effet, les plateformes en tant que service sont extrêmement utiles pour mettre en production rapidement. Leur immense avantage est d'automatiser la mise en production de telle sorte que le développeur n'ait pas réellement besoin de savoir ce qui se passe sur les serveurs.

Tout est parfait dans le meilleur des mondes lorsque l'application est petite et assez standard. Mais petit poisson deviendra grand ! Il aura besoin d'un bocal plus vaste, de nourriture spéciale et de compagnons.

Plus votre application grandira, plus vous voudrez en modifier l'infrastructure. Il devient alors moins intéressant d'utiliser une plateforme en tant que service. Leur prix dépend souvent du nombre de serveurs dont vous avez besoin, de la bande passante, de la mémoire... Plus votre application deviendra gourmande en ressources, plus cela sera cher.

La seconde raison est la maîtrise de l'environnement. Par exemple, utiliser une base MySQL sur Heroku est plus cher qu'une base PostgreSQL lorsque vous démarrez un projet. Vous êtes donc tenté d'utiliser ce second système de gestion de bases de données pour les mauvaises raisons !

Maintenir une infrastructure est coûteux et ardu. Il faut s'occuper des ordinateurs, s'assurer qu'aucune coupure de courant n'intervienne, que le réseau soit efficace, que les données soient protégées, etc.

Imaginez que vous souhaitiez héberger le site de votre tante sur votre ordinateur. C'est possible ! Vous le transformez en serveur, vous vous assurez qu'il est toujours connecté à Internet et effectuez quelques configurations basiques. TADA ! 🎉 Il est accessible.

Il est tard, vous éteignez l'ordinateur. C'est fini : plus personne ne peut réserver des cookies faits maison. Tristesse. Vous redémarrez votre ordinateur et vous vous résignez à voir la vérité en face : vous avez désormais de grandes responsabilités. Vous devez vous assurer des éléments suivants :

  • Votre ordinateur ne tombera jamais en panne. JAMAIS !

  • Toutes les données du site web seront toujours accessibles. Aucun virus ne les effacera jamais.

  • Le disque dur sera toujours en service.

  • Il n'y aura jamais de coupure de courant (adieu les orages !).

  • Votre ordinateur sera toujours connecté à Internet en très haut débit.

  • Vous ne renverserez jamais de café sur le ventilateur, votre nièce ne téléchargera jamais ce documentaire vérolé sur Justin Bieber et votre frère n'ouvrira jamais ce mail alléchant qui finira par effacer tous vos dossiers.

À présent, placez votre ordinateur dans un contexte professionnel et imaginez que vous devez en gérer une dizaine, évidemment accessibles par vos collègues. Non, vous n'avez pas envie d'être responsable de tout cela. Pas tout de suite en tous cas.

C'est pourquoi de nombreuses plateformes sous-traitent la gestion de l'infrastructure. C'est ce que nous appelons le Cloud ! Les ressources informatiques étant accessibles par Internet uniquement, et non plus en physique, les développeurs ont inventé Le Nuage. Avant, vous aviez des clés USB. Mais ça, c'était avant ! Aujourd'hui, vous utilisez Dropbox.

En vérité, il existe plusieurs types de Cloud différents, selon la latitude de configuration que vous souhaitez :

  • SAAS (Software as a Service) : une plateforme web avec laquelle vous interagissez via un site web. Vous n'avez aucun moyen de connaître l'état des serveurs. Exemples : Google Drive, One Drive, Dropbox...

  • PAAS (Plateform as a Service) : une plateforme web à qui vous confiez votre code et qui configure les serveurs pour vous de manière automatique. Vous avez plus ou moins de maîtrise sur les serveurs. Exemple : Heroku, Clever Cloud.

  • IAAS (Infrastructure as a Service) : une entreprise qui s'occupe de l'infrastructure pour vous. Vous avez loué un espace sur un serveur et vous y installez ce que vous souhaitez. Rien n'est fait pour vous, sauf si vous le souhaitez. Exemples : Amazon Web Services, Openstack...

Dans ce cours, vous découvrirez comment configurer un serveur virtuel dans une infrastructure en tant que service. Nous utiliserons Digital Ocean pour illustrer les différentes étapes du cours mais vous pouvez choisir un autre hébergeur.

Un serveur virtuel ? Quelle est la différence avec un serveur dédié ?

Un ordinateur peut contenir plusieurs serveurs virtuels. Si vous désirez contrôler l'intégralité d'une machine physique, il faut choisir un serveur dédié, mais c'est bien plus cher.

Projet exemple

Pour les besoins de ce cours, nous développerons progressivement le projet suivant.

Vous venez juste de découvrir l'administration système et vous n'avez qu'une envie : mettre en pratique ce que vous avez appris ! Cela tombe bien, vous avez mis en production une application pour disquaire il y a peu. Vous étiez alors passé par Heroku. Vous décidez de migrer cette application pour l'héberger sur votre propre serveur.

Voici les fichiers source.

Créez un fork du projet dès maintenant en cliquant sur le bouton "Fork".

Configurez un espace serveur

Dans ce chapitre vous découvrirez comment vous connecter à un serveur distant en SSH et comment le configurer correctement.

Afin de déployer l'application sur un serveur, il faut déjà l'acheter ! Pour l'instant vous n'avez pas besoin de la puissance d'un ordinateur entier, vous pouvez donc opter pour un hébergement mutualisé. Concrètement, cela signifie que vous réservez une petite partie d'un serveur.

Digital Ocean est une entreprise de Cloud Computing qui offre de nombreux services. Simple d'utilisation, elle propose des serveurs à des prix très raisonnables. Pour les besoins de ce cours, je réserverai un serveur à 5$ par mois.

Commencez par vous créer un compte puis connectez-vous à l'interface d'administration.

Création du serveur

Un espace serveur s'appelle un "Droplet" dans le jargon de Digital Ocean. Pour en réserver un, cliquez sur "Create Droplet".

La plateforme vous invite à sélectionner plusieurs options :

  • Image : Digital Ocean peut installer, à votre place, le système d'exploitation de votre choix.

  • Taille : caractéristiques du serveur à réserver. Vous choisissez l'espace alloué à votre application : la mémoire vive (CPU), la RAM et la bande passante. Sélectionnez l'option la moins chère pour l'exercice.

  • Ajouter un bloc de stockage : laissez vide, vous n'en avez pas besoin.

  • Localisation des serveurs : sélectionnez Amsterdam ou Londres. Le serveur doit être le plus proche possible des utilisateurs finaux.

Enfin, Digital Ocean vous demande la clé publique. Si vous en avez déjà une, regardez dans le répertoire  Users/vous/.ssh et copiez la clé présente dans le document id_rsa.pub.

$ cd /Users/celinems/.ssh/
$ cat id_rsa.pub

Puis collez ce code dans Digital Ocean et, enfin, choisissez un nom de serveur.

Cliquez sur Suivant puis attendez que le droplet soit créé !

Activer le pare-feu (Firewall)

Un pare-feu, Firewall en anglais, est un logiciel qui détermine les connexions autorisées dans un réseau. Vous pouvez ainsi dire : "Mon réseau peut recevoir les requêtes en SSH mais pas en HTTP".

Dans le cas d'une application Django, il est intéressant de ne donner que deux méthodes d'accès : SSH et HTTP. Ainsi, les personnes externes voulant se connecter à la base de données (qui est sur un autre port) ne pourront pas le faire !

Il est très simple d'activer un pare-feu sur Digital Ocean ! Dans l'interface d'administration, cliquez sur Networking > Firewalls > Create firewall.

Les règles de réception, ou Inbound Rules, spécifient les sources de trafic autorisées en fonction des ports. Si aucune source de trafic n'est permise, tout le trafic entrant sera refusé.

Concrètement, aucune requête HTTP ne pourra être réalisée sur le serveur. Un peu problématique pour une application Django !

Dans Inbound Rules, sélectionnez SSH (port 22) et HTTP (port 80).

Ajoutez le nom de votre droplet dans "Apply to Droplets" et cliquez sur "Create Firewall".

Connexion en SSH

Via l'utilisateur Root

Comment vous connecter au serveur ? Nous allons le faire via SSH, un programme informatique et un protocole de communication sécurisé. Concrètement, vous allez interagir avec le serveur distant via votre console.

La commande est ssh, suivie du nom d'utilisateur et de l'adresse IP. Cela ne vous parle pas ? C'est normal. :)

Commençons par l'utilisateur. Digital Ocean a créé un utilisateur unique qui a tous les droits sur le serveur. Il peut tout faire ! Il s'appelle root.

L'adresse IP, elle, est l'identifiant unique du serveur virtuel sur Internet. Revenez à Digital Ocean : elle figure dans l'interface d'administration.

Connectez-vous en ssh en tapant la commande root@IPDUSERVEUR.

Par exemple :

$ ssh root@178.62.117.192
The authenticity of host '178.62.117.192 (178.62.117.192)' can't be established.
ECDSA key fingerprint is SHA256:zNopc54JCaopqPC0TXb8NtOzc3sEbj7B6Ekby92UARI.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '178.62.117.192' (ECDSA) to the list of known hosts.

Étant donné que vous vous connectez pour la première fois, le serveur ne vous connaît pas encore. Il affiche donc un message d'étonnement. Pour vous connecter, tapez yes.

Puis le message d'accueil suivant apparaît :

Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-87-generic x86_64)

* Documentation:  https://help.ubuntu.com
* Management:     https://landscape.canonical.com
* Support:        https://ubuntu.com/advantage

Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@disquaire:~#

Vous êtes désormais dans le serveur ! Youhou !

Via un nouvel utilisateur

Comme vous pouvez le voir, l'invite de commande ne commence pas par $ mais par #. Pourquoi ? Car vous êtes super utilisateur. Créez un utilisateur ayant des droits restreints en tapant la commande adduser puis en renseignant les différentes valeurs demandées.

root@disquaire:~# adduser celinems
Adding user `celinems' ...
Adding new group `celinems' (1000) ...
Adding new user `celinems' (1000) with group `celinems' ...
The home directory `/home/celinems' already exists.  Not copying from `/etc/skel'.
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for celinems
Enter the new value, or press ENTER for the default
    Full Name []: Celine
    Room Number []:
    Work Phone []:
    Home Phone []:
    Other []:
Is the information correct? [Y/n] Y

Le compte utilisateur est désormais créé et ce dernier bénéficie de privilèges normaux. Cela signifie qu'il ne peut pas faire de tâches importantes comme installer certains logiciels.

Nous pouvons bien sûr conserver le super utilisateur pour effectuer les tâches importantes et l'utilisateur normal pour les autres, mais cela peut être assez fastidieux de faire l'aller-retour.

Pour éviter cela, vous pouvez ajouter l'utilisateur au groupe des super utilisateurs (sudo). Cela lui permettra d'utiliser la commande sudo.

root@disquaire:~# gpasswd -a celinems sudo
Adding user celinems to group sudo

Allez, on y est presque !

Lorsque vous avez réservé le droplet sur Digital Ocean, vous avez entré votre clé publique. Le logiciel l'a associée au compte root  pour vous permettre de vous connecter en SSH avec cet utilisateur. Mais qu'en est-il de votre nouvel utilisateur ? Pour le moment, aucune clé ne lui est reliée. Un peu dommage, n'est-ce pas ?!

Ajoutez donc dès maintenant la clé publique dans le répertoire dédié à l'utilisateur.

Dans le serveur, commencez par déclarer que vous êtes le nouvel utilisateur :

root@disquaire:~# su - celinems
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
celinems@disquaire:~$

Bien ! Vous pouvez désormais agir en tant qu'utilisateur ! Remarquez également que vous avez été automatiquement placé dans le répertoire de l'utilisateur et que vous n'êtes plus à la racine de l'ordinateur :

celinems@disquaire:~$ pwd
/home/celinems

Créez le dossier .ssh dans le répertoire de votre utilisateur et changez les permissions d'accès.

celinems@disquaire:~$ mkdir .ssh
celinems@disquaire:~$ chmod 700 .ssh

Créez ensuite un nouveau document pour héberger votre clé publique : authorized_keys. Collez-y la clé publique copiée précédemment.

Puisque la connexion se fait uniquement via le terminal, vous ne pouvez pas utiliser d'éditeur de texte graphique. Par défaut, Nano et Vim sont installés. Il s'agit d'éditeurs de texte utilisables dans votre console. Pour éditer un fichier de texte avec Vim, utilisez la commande vi nomdufichier. Comme ceci :

celinems@disquaire:~$ vi .ssh/authorized_keys

Dans Vim, tapez sur la touche esc (échappe) de votre clavier, puis :wq (write and quit) pour enregistrer les changements et quitter l'éditeur.

Puis changez les permissions d'accès au fichier authorized_keys et quittez l'utilisateur en tapant exit :

celinems@disquaire:~$ chmod 600 .ssh/authorized_keys
celinems@disquaire:~/.ssh$ exit
logout
root@disquaire:~#

Vous êtes revenu au super utilisateur !

Étant donné que vous pouvez vous connecter en SSH via le nouvel utilisateur, il est tout à fait inutile de conserver la connexion via le root. Vous pouvez supprimer cette méthode de connexion en modifiant la configuration générale du programme SSH. Elle se trouve dans le document sshd_config qui regroupe les réglages du programme.

Le dossier etc regroupe tous les programmes installés dans le serveur et accessibles à tous les utilisateurs. SSH en fait partie !

root@disquaire:~# vi /etc/ssh/sshd_config

Puis trouvez la ligne PermitRootLogin yes. Elle spécifie que la connexion par l'utilisateur Root est possible. Remplacez yes par no.

PermitRootLogin no

Il faut recharger le programme afin que les changements soient pris en compte. Tapez la commande suivante :

root@disquaire:~# service ssh reload

Tester que tout fonctionne

Ne vous déconnectez pas tout de suite ! Si vous le faites et que la configuration de l'autre utilisateur est erronée, vous risquez de rester dehors pour toujours ! 😭

Afin d'éviter cela, ouvrez un nouveau terminal pour vérifier que tout fonctionne. Connectez-vous en tapant la commande ssh nomutilisateur@IP.

ssh celinems@178.62.117.192
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-87-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

celinems@disquaire:~$

Tout fonctionne !! \o/

Téléchargez l'application sur le serveur distant

Le serveur est configuré, mais ce n'est pas terminé ! Dans ce chapitre vous allez apprendre à installer les paquets nécessaires au bon fonctionnement de Django. Vous réaliserez également la migration de données, c'est-à-dire que vous importerez les données qui sont actuellement sur Heroku.

Allez, c'est parti !

Télécharger l'application sur le serveur

Librairies utiles

Le projet exemple de ce cours est écrit en Python (évidemment !) et utilise le système de gestion de bases de données PostgreSQL. Or, si vous tapez la commande python dans le serveur, vous verrez que le programme n'est pas installé. Cela ne va pas du tout ! Il faut y remédier !

celinems@disquaire:~$ sudo apt-get update
celinems@disquaire:~$ sudo apt-get install python3-pip python3-dev libpq-dev postgresql postgresql-contrib
Téléchargez l'application

Vous avez déjà forké l'application exemple sur GitHub. Quoi de plus simple que de la cloner ? Téléchargez l'application via Git dans le répertoire de l'utilisateur.

celinems@disquaire:~/disquaire$ git clone votredepot.git
Cloning into 'decouvrez_django'...
remote: Counting objects: 740, done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 740 (delta 3), reused 11 (delta 1), pack-reused 724
Receiving objects: 100% (740/740), 1002.34 KiB | 0 bytes/s, done.
Resolving deltas: 100% (346/346), done.
Checking connectivity... done.

Bravo ! Les fichiers sont sur le serveur ! Il est temps d'activer un environnement virtuel pour lancer Django !

Environnement virtuel et dépendances

Installez Pip, Virtualenv et créez un nouvel environnement virtuel.

celinems@disquaire:~/$ sudo apt install virtualenv
celinems@disquaire:~/$ virtualenv env -p python3
celinems@disquaire:~/$ source env/bin/activate
(env) celinems@disquaire:~/$

Puis installez les dépendances du projet en tapant la commande suivante :

(env) celinems@disquaire:~/$ pip install -r disquaire/requirements.txt

Migrer les données

Création de la base de données

Il est temps de passer à la base de données ! Vous devez créer deux éléments : la base en elle-même et un utilisateur qui pourra y accéder. Il ne s'agit plus ici de l'utilisateur du système mais d'un nouveau dédié à PostgreSQL.

Connectez-vous en tant qu'administrateur à la console PostgreSQL en tapant la commande sudo -u postgres psql. Puis créez une nouvelle base de données.

celinems@disquaire:~$ sudo -u postgres psql
psql (9.5.8)
Type "help" for help.

postgres=# CREATE DATABASE disquaire;
CREATE DATABASE

Afin de créer l'utilisateur, rien de plus simple ! Utilisez la commande CREATE USER vous WITH PASSWORD 'motdepasse';.

postgres=# CREATE USER celinems WITH PASSWORD '0+0=LaTeteàT0t0';
CREATE ROLE

Une dernière modification est nécessaire avant de poursuivre. La documentation officielle de Django conseille de changer certains paramètres de la base de données afin d'améliorer la performance des requêtes. En effet, si la base contient déjà la configuration demandée, l'ORM n'aura pas à les indiquer pour toute nouvelle connexion.

postgres=# ALTER ROLE celinems SET client_encoding TO 'utf8';
ALTER ROLE
postgres=# ALTER ROLE celinems SET default_transaction_isolation TO 'read committed';
ALTER ROLE
postgres=# ALTER ROLE celinems SET timezone TO 'Europe/Paris';
ALTER ROLE

Enfin, indiquez à PostgreSQL que l'utilisateur a tous les droits sur la base. Il pourra ainsi lire, écrire et supprimer des données !

postgres=# GRANT ALL PRIVILEGES ON DATABASE disquaire TO celinems;
GRANT

Tapez \q pour quitter la console de PostgreSQL.

Pourquoi nous n'avons pas dû démarrer le serveur PostgreSQL ?

Car il tourne déjà ! :) L'installation de PostgreSQL a lancé un serveur en tâche de fond. De manière générale, lorsque vous installez un service via apt-get, il est lancé automatiquement.

Modification du fichier de configuration

Modifiez alors le fichier settings.py pour y faire figurer les informations de connexion :

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'disquaire',
        'USER': 'celinems',
        'PASSWORD': '0+0=LaTeteàT0t0',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
Génération des fichiers statiques

Django ne génère pas tout seul les fichiers statiques : il faut lui demander de le faire. Générez-les en exécutant la commande ./manage.py collectstatic.

Une erreur apparaît : django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path.

C'est normal ! Ouvrez le fichier de configuration : la constante STATIC_ROOT, nécessaire à la collecte des fichiers statiques, n'existe que si la variable d'environnement ENV est égale à PRODUCTION. Or pour le moment cette variable n'existe pas !

Créez-la tout de suite en tapant la commande suivante :

(env) celinems@disquaire~$ export ENV=PRODUCTION
(env) celinems@disquaire~$ ./disquaire/manage.py collectstatic
# ...
93 static files copied to '/home/celinems/disquaire/disquaire_project/staticfiles'.
Migrations

Lancez les migrations :

(env) celinems@disquaire:~$ ./disquaire/manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, store
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 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 sessions.0001_initial... OK
  Applying store.0001_initial... OK
(env) celinems@disquaire:~$

Je vous ai fait un cadeau : j'ai exporté les données présentes sur le serveur d'Heroku dans le dossier que vous avez téléchargé ! Regardez, elles se trouvent ici : store/dumps/store.json. Comment importer les données dans la base actuelle ? En utilisant la commande loaddata.

(env) celinems@disquaire:~$ ./disquaire/manage.py loaddata disquaire/store/dumps/store.json
Installed 46 object(s) from 1 fixture(s)

Enfin, créez un administrateur :

(env) celinems@disquaire:~$ ./disquaire/manage.py createsuperuser

Comment vérifier que tout a fonctionné ?

Le premier réflexe est bien sûr de lancer un serveur et de naviguer à l'adresse locale.

./disquaire/manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
September 08, 2017 - 16:47:14
Django version 1.11.4, using settings 'disquaire_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Évidemment, rien ne s'affiche ! Vous devez configurer Gunicorn pour qu'il envoie les requêtes entrantes à l'application Django. Cela ne vous parle pas ? C'est normal ! Nous voyons tout cela dans le chapitre suivant.

Utilisez le serveur HTTP Nginx

Dans les chapitres précédents vous avez réservé un espace sur un serveur, téléchargé les différents logiciels et créé la base de données. Votre projet Django est prêt à être montré !

Vous avez lancé le serveur avec brio en tapant la commande python disquaire/manage.py runserver. Malheureusement, l'adresse http://127.0.0.1:8000/ n'affiche rien ! Vous devez utiliser l'adresse IP publique pour y accéder !

L'adresse publique ? Il existe différentes adresses ?

Tout à fait ! Chaque ordinateur possède notamment une adresse privée et une publique.

La première lui est propre et sert à représenter un ordinateur sur un réseau local. Elle n'est pas accessible par Internet.

L'adresse publique, elle, est un identifiant unique qui est bien accessible par Internet. Ainsi, vous pouvez accéder à votre serveur en tapant l'adresse IP publique, donnée par Digital Ocean, mais non l'adresse privée !

Pour en savoir plus, je vous conseille de lire le début du chapitre Analyser le réseau et filtrer le trafic avec un pare-feu.

Alors, comment faire ? Bien entendu, vous n'allez pas changer la configuration de Django. Vous allez configurer un logiciel pour transférer les requêtes effectuées sur l'IP publique à l'IP privée. Il s'agit d'un serveur web ou serveur HTTP.

Il en existe plusieurs et vous avez peut-être déjà entendu parler d'Apache ou de Nginx. Dans ce chapitre, vous découvrirez ce serveur HTTP ! Dans une seconde partie, vous découvrirez comment ajouter un fichier de configuration pour afficher l'application Django.

Engine-X, Nginx pour les intimes

Nginx (que l'on prononce ènjyne-x) est un logiciel libre de serveur web, écrit par Igor Sysoev, dont le développement a débuté en 2002 pour les besoins d'un site russe à très fort trafic (Rambler). Il est rapidement devenu très populaire grâce à sa syntaxe facile à prendre en mains. En septembre 2017, Nginx est utilisé par 20,75% des sites actifs dans le monde selon Netcraft.

Téléchargez-le sur le serveur en tapant la commande suivante :

sudo apt-get install nginx

Nginx étant installé, tout trafic arrivant à votre serveur sera géré par le serveur web. Faites l'essai : tapez l'adresse IP dans votre navigateur !

Vous voyez à présent un message de la part d'Nginx qui indique que tout fonctionne correctement. Afin de comprendre comment ce message a été généré, regardez la configuration par défaut.

Configuration par défaut

La configuration par défaut de Nginx est située dans etc/nginx/sites-enabled/. La voici :

default

##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# http://wiki.nginx.org/Pitfalls
# http://wiki.nginx.org/QuickStart
# http://wiki.nginx.org/Configuration
#
# Generally, you will want to move this file somewhere, and start with a clean
# file but keep this around for reference. Or just disable in sites-enabled.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #
        #       # With php7.0-cgi alone:
        #       fastcgi_pass 127.0.0.1:9000;
        #       # With php7.0-fpm:
        #       fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}


# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
#       listen 80;
#       listen [::]:80;
#
#       server_name example.com;
#
#       root /var/www/example.com;
#       index index.html;
#
#       location / {
#               try_files $uri $uri/ =404;
#       }
#}

Je ne comprends pas bien la syntaxe. Cela ressemble vaguement à du CSS...

Exact ! C'est assez similaire mais la ressemblance est trompeuse. Voyons tout de suite la syntaxe !

Le fichier de configuration contient plusieurs éléments :

  • # : commentaire. Chaque ligne commençant par un croisillon (souvent appelé dièse) ne sera pas évaluée par Nginx. Vous pouvez ainsi donner plus de détails si vous le souhaitez. Les développeurs les utilisent également pour activer ou désactiver facilement certains paramètres.

  • listen 80; : directive. Une ligne commence par un mot-clé. Il prend au moins un argument et se finit par un point-virgule. Contrairement à CSS, chaque mot-clé est séparé de l'argument par un espace et non pas par deux-points. Pensez-y ! ;) On appelle chaque mot-clé une directive.

  • location / {try_files $uri $uri/ =404;} : bloc. Certains arguments de mots-clés contiennent des accolades regroupant d'autres lignes de configuration. Exactement comme en CSS, chaque directive contenue dans l'accolade a une répercussion sur l'argument. Nous appelons cela un bloc.

  • L'indentation n'est pas une obligation mais je vous conseille de garder votre code lisible en ajoutant 4 espaces à gauche pour chaque niveau.

Vous remarquerez également que certaines accolades contiennent elles-mêmes d'autres accolades. Exactement comme en HTML, une balise peut en contenir une autre ! Dans ce cas, les sous-directives auront une répercussion à la fois sur la directive mère et sur la grand-mère.

Maintenant que vous connaissez la syntaxe, regardons de plus près le fichier. Il est organisé en deux parties. La majeure partie contient des directives réellement appliquées. Quant à la fin du fichier, il donne un exemple facile à reprendre. Commençons par expliciter certaines directives avant de détailler l'exemple.

La directive server permet de regrouper les différentes actions à réaliser pour un groupe. Dans le jargon, un groupe est un "serveur virtuel". En savoir plus

La ligne listen 80 default_server; indique que le port 80 est utilisé. Ainsi, tout trafic à l'URL "adresseip:80" sera traité par cet ensemble (mais, par exemple, pas celui utilisant le port :3000 !). En savoir plus

Vous avez remarqué que notre serveur web a affiché une page d'information en HTML. Où se trouve-t-elle ? Si vous cherchez dans le dossier etc/nginx, vous ne la verrez pas. Et pour cause, elle se trouve bien ailleurs ! Nginx sait comment la trouver grâce à root qui indique le dossier de base des fichiers à renvoyer. Ainsi, la directive root /var/www/html; dit : "Tous les documents qui sont concernés par ce groupe sont dans le dossier var/www/html".

Quant à la page d'accueil, elle est désignée par index : index index.html index.htm index.nginx-debian.html;. Regardez dans le dossier /var/www/html : vous y découvrirez effectivement le fichier index.nginx-debian.html.

Pourquoi plusieurs noms de fichiers sont passés en argument ?

Les arguments sont indiqués par ordre de priorité : plus ils sont proches de la directive, plus ils sont prioritaires. Exactement comme en CSS ! Ainsi, la configuration par défaut permet plusieurs noms de fichier.

La directive server_name permet d'indiquer quelle est le nom de domaine, ou l'adresse IP, ciblée par le groupe. Elle prend en paramètre une expression régulière. Vous n'en utiliserez pas dans ce cours, rassurez-vous ! Mais sachez que le symbole _ représente, pour Nginx, tous les noms de domaines. En savoir plus dans la documentation.

La directive suivante est location. Elle fait référence au chemin relatif qui est dans l'URL (l'URI) et prend en argument une expression régulière ou une chaine de caractères. Petite subtilité : si l'argument est une chaîne, l'URI doit commencer par elle. ! Dans cet exemple, seule une barre oblique est indiquée : /. Cela signifie que toutes les requêtes dont l'URI commence par / devront appliquer les directives du bloc. En savoir plus

Enfin, la dernière est try_files. Elle vérifie l'existence des fichiers passés en argument par ordre de priorité. L'exemple indique d'ailleurs : try_files $uri $uri/ =404;. Que va-t-il se passer ? Nginx va chercher le fichier sur le serveur en suivant le chemin passé dans l'URL. Par exemple, il naviguera dans root_file/static/metallica.png si l'URI est /static/metallica.png. S'il est introuvable, il essaiera de trouver le dossier. Par exemple : /static/metallica.png/. Si le fichier est toujours aux abonnés absents, il renverra une erreur 404.

Bravo ! 👏 Vous avez désormais une bien meilleure compréhension de Nginx ! Il est temps de pratiquer !

Mise en application

La fin du fichier de configuration par défaut donne quelques clés pour ajouter un nouveau serveur virtuel mais vous pouvez aller plus loin !

Vous pourriez tout à fait décommenter les directives données en exemple à la fin du fichier de configuration par défaut mais cela deviendrait vite le bazar. Il est d'usage de créer un fichier par site !

Je peux héberger plusieurs sites sur le même serveur ?

Hé oui ! Nginx est très puissant ! Vous pouvez créer autant de serveurs virtuels que vous le désirez. Ainsi, il est tout à fait possible d'utiliser Nginx sur un même serveur pour des sites différents !

Création d'un fichier de configuration

Par convention, les fichiers de configuration sont regroupés dans le dossier sites-enabled. Ils sont tous pris en compte de la même manière. Ouvrez-le : vous y découvrirez un fichier default.

$ vi /etc/nginx/sites-enabled

" ============================================================================
" Netrw Directory Listing                                        (netrw v155)
"   /etc/nginx/sites-enabled
"   Sorted by      name
"   Sort sequence: [\/]$,\<core\%(\.\d\+\)\=\>,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by  x:special
" ==============================================================================
../
./
default@                          --> /etc/nginx/sites-available/default

Vous remarquerez qu'il est un peu spécial : il contient un @... Je vous ai menti, il ne s'agit pas d'un fichier mais d'un lien symbolique ! Le vrai fichier se trouve à l'adresse indiquée par la flèche.

Vous devez donc :

  • créer un nouveau fichier dans sites-available ;

  • ajouter un lien symbolique dans sites-enabled grâce à la commande ln.

C'est parti !

celinems@disquaire:/etc/nginx/$ sudo touch sites-available/disquaire
[sudo] password for celinems:
celinems@disquaire:/etc/nginx/$ sudo ln -s /etc/nginx/sites-available/disquaire /etc/nginx/sites-enabled

Voyons désormais comment lier le trafic entrant à l'application Django. Celle-ci contient d'ailleurs deux formats de fichiers très différents :

  • les fichiers statiques (CSS, JavaScript, images...) ;

  • les fichiers dynamiques qui ont besoin de l'application Django.

Vous allez utiliser Nginx pour les actions suivantes :

  • si un fichier statique est demandé, affiche-le sans passer par l'application ;

  • sinon, redirige le trafic vers l'application Django.

Commencez par rediriger le trafic vers l'application !

Servir l'application Django

Ouvrez le document etc/nginx/sites-available/disquaire que vous venez de créer. Allez, vous avez toutes les clés en main pour le remplir par vous-même ! Faites l'exercice puis regardez ma solution.

nginx/sites-available/disquaire

server { 
        
    listen 80; server_name 178.62.117.192; 
    root /home/celinems/disquaire/;
        
    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
        proxy_pass http://127.0.0.1:8000;
    }
        
}

Détaillons les directives :

  • listen 80 : le port par défaut de toute requête HTTP est 80.

  • server_name 178.62.117.192; : l'adresse IP de mon serveur. Ainsi, je cible toute requête HTTP dont le nom d'hôte est 178.62.117.192. Vous pouvez également passer un nom de domaine !

  • root home/celinems/disquaire/; : tout fichier demandé devra être trouvé dans ce dossier de base. Ainsi, tous les chemins indiqués par la suite seront relatifs à cette racine.

  • la directive location prend deux paramètres : l'URI et les directives à appliquer. Ici, j'indique que toute URI commençant par / doit être redirigée vers l'URL http://127.0.0.1:8000. De fait, toutes les URL seront donc traitées par l'application Django.

Nginx ne recharge pas automatiquement les configurations. Vous devez lui indiquer manuellement en exécutant la commande reload :

celinems@disquaire:/etc/nginx$ sudo service nginx reload

Allez, lancez un serveur en tapant la commande ./manage.py runserver. Puis ouvrez un nouveau terminal et connectez-vous de nouveau en SSH. Ainsi, le serveur Django tournera pendant que vous configurerez Nginx !

Naviguez sur l'URL de votre site : une erreur 400 apparait !

Pas de panique, Django vous avertit simplement que le nom de domaine n'est pas autorisé ! Vous devez modifier le fichier settings.py en conséquence.

settings.py

# ...
ALLOWED_HOSTS = ['178.62.117.192']
# ...

Relancez le serveur : ça fonctionne !

Servir les fichiers statiques

L'application actuelle utilise la librairie WhiteNoise pour servir les fichiers statiques. Étant donné qu'il s'agit d'une tâche dans laquelle Nginx excelle, je vous invite à désactiver la librairie et à utiliser le serveur web.

Supprimez les lignes suivantes du fichier de configuration de l'application Django :

settings.py

MIDDLEWARE = [
    # ...
    # 'whitenoise.middleware.WhiteNoiseMiddleware', => delete this line
]

# ...

if os.environ.get('ENV') == 'PRODUCTION':
    # ...
    # Simplified static file serving.
    # https://warehouse.python.org/project/whitenoise/
    # STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' => delete this line

    # ...

Puis configurez Nginx ! Faites l'exercice et regardez ma solution. Petit conseil : utilisez une structure conditionnelle ! Regardez comment faire dans la documentation.

etc/nginx/sites-available/disquaire

server {
    # ...

    location /static {
        alias /home/celinems/disquaire/disquaire_project/staticfiles/;
    }

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://127.0.0.1:8000;
            break;
        }
    }
}

Retournez voir votre site : il fonctionne parfaitement bien !

Tous les fichiers sont bien servis par Nginx, c'est super !

Heu, non, ce n'est pas du tout super ! On a lancé le serveur de développement, tout le monde a accès à notre configuration !

Effectivement, cela ne va pas du tout ! Nous n'aurions jamais dû lancer le serveur de Django ainsi. La commande ./manage.py runserver lance un serveur de développement, loin d'être efficace en production et surtout non sécurisé !

Comment faire ? Voyons cela dans le prochain chapitre !

Configurez Gunicorn et Supervisor

Dans le chapitre précédent vous avez appris à configurer le serveur HTTP Nginx pour diriger le trafic concernant Django vers l'adresse IP et le port adéquats.

Néanmoins, vous démarriez le serveur de développement ! En production, il faut impérativement utiliser un service plus robuste. De plus, vous l'avez vu, Nginx n'interprète pas de code Python. Il faut donc utiliser un service qui accepte les requêtes HTTP et qui les transfère au code Python écrit pour Django.

Il en existe plusieurs : uWSGI, mod_wsgi, Gunicorn... Le tout est de se décider ! Dans ce chapitre vous allez utiliser Gunicorn, un des serveurs HTTP Python les plus utilisés !

Gunicorn

Gunicorn est le diminutif de Green Unicorn. Il s'agit d'un serveur HTTP Python pour Unix qui utilise les spécifications WSGI (Web Server Gateway Interface). Particulièrement puissant, il est également rapide et facile à configurer. Vous verrez, vous serez épaté·e !

Gunicorn est un logiciel en ligne de commande qui se démarre directement depuis le terminal. Il s'agit d'une librairie Python en source ouverte. Elle s'installe via PyPI mais vous n'avez pas besoin de le faire : elle était présente dans le fichier requirements.txt du projet !

Pour lancer le serveur Gunicorn, rien de plus simple : utilisez la commande gunicorn et passez en paramètre le module de configuration. Encore une fois, vous n'avez rien à faire car Django a tout prévu. Le framework intègre un module WSGI dont les réglages par défaut sont tout à fait convenables. Vous êtes curieux ? Regardez le fichier disquaire_project/wsgi.py. La configuration y figure !

Exécutez donc la commande suivante :

(env) celinems@disquaire:~/disquaire$ gunicorn disquaire_project.wsgi:application
[2017-09-11 23:03:46 +0000] [15335] [INFO] Starting gunicorn 19.7.1
[2017-09-11 23:03:46 +0000] [15335] [INFO] Listening at: http://127.0.0.1:8000 (15335)
[2017-09-11 23:03:46 +0000] [15335] [INFO] Using worker: sync
[2017-09-11 23:03:46 +0000] [15338] [INFO] Booting worker with pid: 15338

Le serveur tourne. Retournez sur l'URL de votre application et rechargez la page : tout fonctionne bien !

À présent, imaginez ce scénario catastrophe : après avoir ajouté des centaines de milliers de CD dans la base de données, le disquaire dont vous gérez le site passe sur une émission à très forte audience. Le nombre de requêtes explose ; en parallèle la base de données utilise énormément de mémoire. Au bout d'un moment, le serveur WSGI tombe à court de mémoire. Conséquence : le service s'arrête, purement et simplement. Vous devez immédiatement le redémarrer à la main ou les utilisateurs seront condamnés à admirer une erreur 502.

Et si un autre service pouvait surveiller que Gunicorn est bien lancé ? Et si ce même service pouvait redémarrer le serveur en cas de défaillance ? Comme la vie serait belle !

Supervisor

Supervisor est un système qui permet de contrôler un ensemble de processus vivant dans un environnement UNIX. Pour faire simple, Supervisor lance des services et les redémarre s'ils échouent. Plutôt sympa !

Avec Supervisor, vous n'auriez plus besoin d'exécuter Gunicorn à la main : il le ferait pour vous !

Il s'agit d'un logiciel qui n'est pas installé par défaut. Faites-le tout de suite :

sudo apt-get install supervisor

Tout comme Nginx, Supervisor lit un fichier de configuration situé dans etc/supervisor. Chaque fichier finissant en .conf et situé dans le chemin /etc/supervisor/conf.d représente un processus qui est surveillé par Supervisor. Créez-en un nouveau :

$ sudo vi /etc/supervisor/conf.d/disquaire-gunicorn.conf

À l'intérieur, ajoutez la commande à exécuter pour démarrer Gunicorn :

disquaire-gunicorn.conf

command=/home/celinems/env/bin/gunicorn disquaire_project.wsgi:application

Mais ce n'est pas tout ! Vous pouvez ajouter très facilement plusieurs options telles que le nom du processus ou l'utilisateur Unix qui exécute la commande (root ou tout autre utilisateur).

De quoi avez-vous besoin pour exécuter Gunicorn ?

  • La commande exacte (ça, c'est bon ! ✅)

  • Le nom d'utilisateur qui a le droit d'exécuter la commande

  • Le chemin duquel est lancé la commande

Lisez la documentation et faites l'essai chez vous avant de lire ma correction !

disquaire-gunicorn.conf

[program:disquaire-gunicorn]
command = /home/celinems/env/bin/gunicorn disquaire_project.wsgi:application
user = celinems
directory = /home/celinems/disquaire
autostart = true
autorestart = true

L'option autostart=true indique à Supervisor que vous souhaitez lancer ce processus au premier démarrage du serveur et autorestart=true que la commande doit être exécutée si le processus est interrompu.

Vous pouvez également passer des variables d'environnement ! Profitez-en et ajoutez-les :

disquaire-gunicorn.conf

environment = ENV="PRODUCTION",SECRET_KEY="unenouvelleclésecrete"

Il est temps de dire à Supervisor de lancer les processus !

$ sudo supervisorctl reread
disquaire-gunicorn: available
$ sudo supervisorctl update
disquaire-gunicorn: added process group

Vérifiez que le processus a bien été ajouté en tapant la commande status :

$ sudo supervisorctl status
disquaire-gunicorn               RUNNING   pid 16309, uptime 0:00:09

Hum, stocker la clé secrète dans le fichier de configuration de Supervisor n'est pas un peu étrange ?

Si ! Nous devrions la conserver dans les réglages de notre application, ce serait plus logique. Dans le prochain chapitre vous découvrirez comment réorganiser le fichier de configuration settings.py pour mettre un peu d'ordre !

Bravo ! Vous pouvez naviguer sur l'URL de votre site et constater qu'il fonctionne de nouveau, même si le serveur Gunicorn n'apparaît plus dans la console !

Intégrez de bonnes pratiques de travail

Séparez les environnements

Vous avez pu le voir, les réglages en production sont différents des réglages en local. Et encore, nous n’avons pas parlé des serveurs de test !

Plus votre projet grossira, et plus les réglages s’affineront. Il est alors d’usage de séparer les fichiers de configuration en fonction de l’environnement. La manière la plus simple d'effectuer cela est de créer un module contenant un fichier par environnement.

Nous n'allons pas travailler directement sur le serveur mais en local. Pourquoi ? Car il est très dangereux de manipuler les fichiers de l'application en production. Si un utilisateur lance une requête, les résultats peuvent être assez étonnants.

Si ce n'est pas déjà fait, clonez en local le dépôt GitHub que vous avez forké précédemment.

Environnement local

Créez un nouveau module :

$ mkdir disquaire_project/settings
$ touch disquaire_project/settings/__init__.py

Par commodité, les réglages par défaut seront ceux de développement. Vous pourrez ensuite ajouter un fichier par environnement pour écraser les réglages par défaut.

Copiez l'intégralité de settings.py et collez-le dans __init__.py. Quand c'est fait, vous pouvez supprimer settings.py.

Puis parcourez le fichier __init__.py pour ne garder que les réglages spécifiques à votre environnement de développement local. Vous pouvez par conséquent supprimer les structures conditionnelles if os.environ.get('ENV') == 'PRODUCTION'

settings/__init__.py

"""
Django settings for disquaire_project project.

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

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

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

import os


# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '4i&amp;u(!%shd*0-3$ls)fohsjsd48t(gu%1-ch_wyzk7@#n3bd8e'
# '-~aO;| F;rE[??/w^zcumh(9'


# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True


ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'store.apps.StoreConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'debug_toolbar',
]

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',
    'django.middleware.locale.LocaleMiddleware',
    'debug_toolbar.middleware.DebugToolbarMiddleware',
]

ROOT_URLCONF = 'disquaire_project.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 = 'disquaire_project.wsgi.application'


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

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql', # on utilise l'adaptateur postgresql
        'NAME': 'disquaire', # le nom de notre base de données créée précédemment
        'USER': 'celinems', # attention : remplacez par votre nom d'utilisateur !!
        'PASSWORD': '',
        'HOST': '',
        'PORT': '5432',
    }
}



# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'fr'

TIME_ZONE = 'Europe/Paris'

USE_I18N = True

USE_L10N = True

USE_TZ = True


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

STATIC_URL = '/static/'

# Django debug toolbar
INTERNAL_IPS = ['127.0.0.1']

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

Environnement de production

Le fichier concernant les réglages de production est particulier car il contient des informations sensibles : les identifiants de connexion à la base de données, la clé secrète... Vous ne voulez surtout pas qu'elles se retrouvent sur GitHub !

Donc voici ce que je vous propose : ce fichier de configuration ne doit exister que sur le serveur et nulle part ailleurs. Vous pouvez donc :

  • créer un fichier .gitignore pour indiquer à Git de ne pas tracker production.py ;

  • envoyer sur GitHub les modifications que vous venez juste d'apporter ;

  • puller sur le serveur ces mêmes modifications ;

  • créer un fichier production.py sur le serveur et y ajouter les informations.

C'est parti !

$ touch .gitignore

.gitignore

disquaire_project/settings/production.py
__pycache__
disquaire_project/staticfiles/
$ git add .gitignore disquaire_project/settings
$ git commit -m "new settings configuration"
$ git push origin master

Puis rendez-vous sur le serveur :

celinems@disquaire:~/disquaire$ git pull origin master

À présent, ajoutez-y un nouveau document qui regroupera les informations de production :

$ celinems@disquaire:~/disquaire$ touch disquaire_project/settings/production.py

settings/production.py

from . import *

SECRET_KEY = '-~aO;| F;rE[??/w^zcumh(9'
DEBUG = False
ALLOWED_HOSTS = ['178.62.117.192']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql', # on utilise l'adaptateur postgresql
        'NAME': 'disquaire', # le nom de notre base de données créée précédemment
        'USER': 'celinems', # attention : remplacez par votre nom d'utilisateur !!
        'PASSWORD': '0+0=LaTeteàT0t0',
        'HOST': '',
        'PORT': '5432',
    }
}

Comment indiquer à Django que vous voulez utiliser ce fichier de configuration et non settings.py ?

Django cherche la variable d'environnement DJANGO_SETTINGS_MODULE pour connaître le module qui contient les configurations. Par défaut, il s'agit de votreprojet.settings.

Le changer est un jeu d'enfant puisque nous utilisons Supervisor !

celinems@disquaire:~/disquaire$ sudo vi /etc/supervisor/conf.d/disquaire-gunicorn.conf

Puis modifiez la ligne :

environment = DJANGO_SETTINGS_MODULE='disquaire_project.settings.production'

Indiquez à Supervisor que le fichier a été modifié :

(env) celinems@disquaire:~/disquaire$ sudo supervisorctl reread
disquaire-gunicorn: changed
(env) celinems@disquaire:~/disquaire$ sudo supervisorctl update
disquaire-gunicorn: stopped
disquaire-gunicorn: updated process group
(env) celinems@disquaire:~/disquaire$ sudo supervisorctl status
disquaire-gunicorn               RUNNING   pid 21764, uptime 0:00:09

Vous avez bien avancé et vous pouvez être fier de vous ! Dans le prochain chapitre vous apprendrez à lancer des tests avant d'envoyer en production ! 🔬

Intégrez des changements automatiquement

Les équipes de développement intègrent souvent des règles de fonctionnement qui les aident à produire du code de qualité. L'une d'elles est le "git workflow" :

  • Une nouvelle fonctionnalité est décidée. Le développeur (appelons-le Didier) crée une nouvelle branche dédiée.

  • Il y réalise les changements nécessaires et envoie régulièrement son code sur GitHub (ou Gitlab...).

  • Lorsque la fonctionnalité est prête à être mise en ligne, il crée une pull request pour intégrer sa branche dans la master.

  • Une développeuse de son équipe (appelons-la Sonia) examine la pull request (PR). Elle fait un pull en local et lance les tests pour vérifier que tout fonctionne.

  • Quand les tests sont terminés, elle laisse des commentaires sur la PR.

  • Didier intègre les corrections.

  • Lorsque tout est prêt, Sonia valide la PR. La branche de développement est alors fusionnée dans la branche master.

Si les tests sont tous au vert, quelle a été la valeur ajoutée de Sonia ? Elle a perdu 10 minutes à charger le code en local, lancer les tests et valider qu'ils passaient. Elle a été interrompue dans son travail pour une tâche qui pourrait être facilement automatisable !

Étant donné que cela la déconcentre, elle a bloqué un moment dans son agenda afin d'examiner la PR tranquillement. Mais elle ne peut pas le faire souvent. Didier s'est alors dit : "Je ne vais pas faire beaucoup de PR, il vaut mieux que je regroupe tous les changements en une".

Progressivement, les PR grossissent et deviennent ingérable. Sonia perd du temps à lancer les tests sur son ordinateur et ne peut pas se concentrer sur l'essentiel : le code.

Et si vous pouviez utiliser un service qui exécuterait les tests quand une PR est acceptée ? Vous avez deviné, je vous parle d'intégration continue.

Intégration continue (CI)

On appelle "intégration" les tâches à faire lorsqu'un·e développeur·se a fini de coder une nouvelle fonctionnalité. Concrètement, il/elle doit s'assurer :

  • que son code ne contient aucun bug ;

  • que le reste de l'application continue de bien fonctionner si il/elle intègre ses changements ;

  • que la fonctionnalité correspond aux bonnes pratiques de développement de l'équipe.

Autrement dit, le/la développeur·se s'arrête d'écrire du code pour tester différents aspects de l'application. Plus le projet est grand, plus le risque d'erreur est élevé et plus la tâche est longue.

L'intégration continue (Continuous Integration) est une des pratiques les plus répandues des méthodologies de projet Agile. Elle permet de diminuer le risque d'erreur en production et d'améliorer le temps consacré à l'intégration.

Une équipe qui pratique l'intégration continue vise deux objectifs:

  • réduire à presque zéro la durée et l'effort nécessaire à chaque épisode d'intégration ;

  • pouvoir, à tout moment, fournir un produit exploitable.

Il est assez simple d'utiliser un service d'intégration continue qui va lancer des tests automatisés lorsqu'une pull request est acceptée. Voici comment cela se déroule :

  • Une pull request est créée.

  • Le service d'intégration continue lance l'application sur un serveur de test.

  • Si les tests échouent, le service indique que la fonctionnalité n'est pas prête à être intégrée.

  • Si les tests passent, le service affiche un voyant vert.

  • Sonia peut alors relire sereinement la pull request. Si elle la valide, le code est intégré.

Il existe plusieurs services qui proposent de l'intégration continue : Ansible, Jenkins, Travis... Le plus simple à utiliser est Travis. Vous verrez, cela ira tout seul !

Travis

Travis est un service d'automatisation qui s'interface parfaitement avec GitHub. Il permet de créer un environnement de développement rapidement, d'y faire tourner une application et d'y lancer des tests.

Vous souvenez-vous de toutes les manipulations que vous avez effectuées dans la première partie de ce cours ? Travis le fait pour vous ! Cadeau !

Pourquoi Travis est-il si facile à prendre en main ? Car il intègre une configuration par défaut modifiable très aisément. Concrètement, voici les différentes étapes à mettre en place pour utiliser Travis :

  • indiquer à Travis de surveiller un dépôt GitHub ;

  • intégrer un fichier de configuration .travis.yml dans le projet GitHub.

Et c'est tout ! Incroyable, n'est-ce pas ?!

Voici, par exemple, un fichier .travis.yml :

language: python
python:
  - "3.5"
# command to install dependencies
install:
  - pip install -r requirements.txt
# command to run tests
script:
  - pytest # or py.test for Python versions 3.5 and below

Ce script va installer les dépendances listées dans le fichier requirements.txt puis exécuter la commande  pytest. Fastoche. :)

Il est temps de pratiquer ! Voyons tout de suite comment lier un compte Travis à GitHub.

Activer l'intégration continue avec Travis CI

Rendez-vous à l'adresse https://www.travis-ci.com/ puis cliquez sur "Sign in with GitHub". Connectez-vous en utilisant votre identifiant et votre mot de passe GitHub.

Vous êtes redirigé vers le tableau de bord de Travis. Il regroupe les différents projets pour lesquels vous avez activé l'intégration continue. Pour l'instant vous n'en avez aucun, quelle tristesse ! Cliquez sur la croix à droite de l'onglet "My repositories" pour en ajouter un.

Travis affiche alors tous les dépôts publics de votre compte GitHub. Trouvez celui que vous avez forké tout à l'heure et activez-le en cliquant sur la croix grise.

C'est tout ! :) Vous devez maintenant indiquer à Travis les commandes à exécuter lors du build.

Étapes d'un déploiement sur le serveur de test

Le processus de déploiement que suit Travis est toujours le même :

  • installation : il commence par installer les logiciels demandés ;

  • exécution : puis il exécute les tests.

Dans le fichier de configuration, l'installation est désignée par le mot install et l'exécution par script.

À noter que vous pouvez également demander à Travis d'exécuter certaines commandes avant ou après ces étapes :

  • before_install : avant d'exécuter les commandes d'installation, fais ça.

  • before_script : quand les étapes d'installation ont été correctement effectuées, fais ça.

  • after_success or after_failure : si les scripts échouent ou réussisent, fais ça.

  • after_script : quand les scripts ont été exécutés, fais ça.

Avant de continuer, prenez cinq minutes pour réfléchir à ces étapes puis appliquez-les au projet en cours. Quelles commandes voulez-vous exécuter ? À quel moment ?

Pour ma part, je vois les étapes suivantes :

  • Avant d'exécuter les tests, Travis installe les dépendances listées dans requirements.txt ;

  • Puis Travis exécute la commande suivante : ./manage.py test.

Mais ce n'est pas tout ! Comment lui indiquer qu'il doit installer PostgreSQL et lancer le service ? Et la version de Python ? Les variables d'environnement ? Pour répondre à toutes ces questions, lisez la documentation avant de poursuivre.

Créer un fichier de configuration

Créez un nouveau document, .travis.yml au même niveau que manage.py.

.travis.yml

language: python
python:
  - '3.5'

before_script:
  - pip install -r requirements.txt

env: DJANGO_SETTINGS_MODULE="disquaire_project.settings"

services:
  - postgresql

script:
  - ./manage.py test

Mais il manque énormément d'éléments ! Comment utiliser PyPI si Virtualenv n'est pas installé ?

Travis est un peu magique ! Si vous lui indiquez le langage, il installera automatiquement certains outils qu'il estime indispensables, tel que Pip et Virtualenv. Il va même activer l'environnement virtuel pour vous !

Le fichier est prêt à être envoyé sur GitHub mais prenez quelques instants pour réfléchir à ce que vous souhaitez faire. À quel moment, dans votre cycle de développement, les tests doivent-ils se déclencher ? Vous avez plusieurs possibilités :

  • Lorsqu'une modification est apportée sur n'importe quelle branche. Autrement dit tout nouveau git push origin unebranche lancera l'exécution des tests.

  • Lorsqu'un push est réalisé sur une branche en particulier.

  • Lorsqu'une pull-request a été ajoutée ou mise à jour.

  • ...

Pour ma part, je décide de lancer un build à chaque fois que je pousserai du code sur la branche staging. Ainsi, si les tests passent je pourrais créer une pull-request pour demander à fusionner cette branche dans master.

Travis permet cela de manière assez simple :

# ...

# safelist
branches:
  only:
    - staging

# ...

Et voilà, le tour est joué ! Vous n'avez plus qu'à envoyer ce fichier sur GitHub !

$ git add .travis.yml
$ git commit -m "Add travis file"
$ git push origin master

Allez, vous êtes prêt à tester ? :)

Retournez dans l'interface d'administration de Travis. Rien n'apparaît et c'est bien normal : vous avez demandé à ce qu'un build ne soit exécuté que lorsqu'un changement sur la branche staging est détecté.

Créez donc une nouvelle branche et poussez-la :

(env) celinems@disquaire:~/disquaire$ git checkout -b staging
(env) celinems@disquaire:~/disquaire$ git push origin staging

Revenez à Travis. Un build se lance ! Youpi ! Malheureusement, il s'interrompt au bout de quelques minutes.

Saperlipopette, qu'a-t-il bien pu se passer ?

Lisez les logs affichés par Travis :

# ...
File "/home/travis/virtualenv/python3.5.3/lib/python3.5/site-packages/django/db/backends/base/base.py", line 189, in connect
  self.connection = self.get_new_connection(conn_params)
File "/home/travis/virtualenv/python3.5.3/lib/python3.5/site-packages/django/db/backends/postgresql/base.py", line 176, in get_new_connection
  connection = Database.connect(**conn_params)
File "/home/travis/virtualenv/python3.5.3/lib/python3.5/site-packages/psycopg2/__init__.py", line 130, in connect
  conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
django.db.utils.OperationalError: FATAL:  role "celinems" does not exist

The command "./manage.py test" exited with 1.
Done. Your build exited with 1.

Hum... Avez-vous trouvé l'erreur ?

Avant d'exécuter les tests, Django crée une base de données spécifique pour éviter de mélanger les torchons et les serviettes. Or actuellement les réglages par défaut sont ceux de développement !

Malheureusement, l'utilisateur PostgreSQL créé par Travis ne correspond pas au vôtre... La documentation de Travis est très explicite sur ce sujet.

Que faire ? Créez simplement un nouveau fichier dans settings. Heureusement que vous avez déjà un module dédié ! ;)

Comme pour les réglages de production, créez un nouveau fichier travis.py dans le dossier settings.

settings/travis.py :

from . import *

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': '',
        'USER': 'postgres',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    },
}

Comme tout à l'heure, utilisez la variable d'environnement DJANGO_SETTINGS_MODULE. Il suffit de l'ajouter à  .travis.yml  !

language: python
python:
  - '3.5'

# safelist
branches:
  only:
    - staging

before_script:
  - pip install -r requirements.txt

services:
  - postgresql

env: DJANGO_SETTINGS_MODULE=disquaire_project.settings.travis

script:
  - ./manage.py test

Allez, on retente ?

Envoyez les fichiers modifiés sur GitHub.

$ git add .travis.yml disquaire_project/settings/travis.py
$ git commit -m "update travis config"
$ git push origin staging

Retournez sur l'interface de Travis et admirez le travail !

Surveillez l'activité d'un serveur

Vous avez configuré avec succès Travis pour déployer en continu, beau travail ! Il reste une dernière étape : surveiller que tout se passe bien sur le serveur.

Supervisor relancera Gunicorn si ce dernier doit quitter automatiquement, mais ce n'est pas le seul danger qui guette votre projet ! Un bon SysAdmin regarde l'état de ses serveurs et optimise les requêtes.

Monitorer un serveur, à quoi ça sert ?

Surveiller l'activité d'un serveur ne revient pas simplement à regarder si les composants fonctionnent bien. Il est bon de suivre l'usage de la mémoire vive et de la bande passante pour anticiper les soucis.

Bien sûr, vous pouvez suivre cet usage en temps réel directement dans la console. Tapez top pour l'expérience, vous aurez l'impression d'entrer dans la matrice ! :)

Mais vous n'allez pas rester le nez rivé devant votre console pour surveiller que la mémoire n'est pas saturée. C'est pourquoi il est bon de faire confiance à un service tiers qui va surveiller votre serveur et vous avertir en cas d'incident. Digital Ocean intègre cette fonctionnalité par défaut mais New Relic Infrastructure est également très prisé.

Il existe également un second type d'activité que vous souhaiterez surveiller : votre application Django ! Nous n'avons pas beaucoup parlé de logs, et pourtant il s'agit d'un sujet essentiel ! Ils vous permettent de remonter le temps, de comprendre ce qui a pu se passer. Vous pouvez les utiliser pour indiquer des erreurs mais également un paiement, l'inscription d'un nouvel utilisateur ou une recherche effectuée. Comme pour l'activité de votre ordinateur, vous pouvez utiliser le client de log de Django ou utiliser un service tiers.

Dans la suite de ce chapitre je vous montrerai comment configurer Sentry, un SAAS en source ouverte qui vous permet d'afficher vos logs (et bien d'autres éléments !) d'une bien belle manière.

Mais commençons par nous intéresser au "hardware" !

Activer le monitoring de Digital Ocean

Installez l'agent de Digital Ocean sur votre serveur :

$ curl -sSL https://agent.digitalocean.com/install.sh | sh

Puis allez dans l'interface d'administration de Digital Ocean. Dans votre droplet vous avez déjà accès à bien des informations !

  • CPU, ou processeur en français, est le composant qui exécute les instructions des programmes. Ici vous pouvez visualiser le pourcentage de puissance utilisé.

  • Memory représente la mémoire vive utilisée.

  • Disk I/O  (disk input/output) est la quantité de données lues ou écrites sur le disque dur du serveur par seconde.

  • Disk usage est la quantité de données stockées dans le disque dur du droplet.

  • Bandwidth (bande passante) représente la quantité de données transférée entre le droplet et des ressources externes.

  • Top processes affiche les processus les plus exigeants en terme de mémoire vive (Memory) et de processeur (CPU).

Dans ces graphiques, plus la ligne est proche de 100% plus cela vous indique que vous devriez envisager le plan supérieur.

Le principe du monitoring n'est pas de regarder les données pendant des heures mais plutôt de configurer des alertes pour recevoir les informations adéquates.

Cliquez sur "Monitoring" puis sur "Create alert policy". Comme vous pouvez le constater, vous avez le choix !

En général, nous surveillons les éléments suivants :

  • le CPU et la RAM ne sont pas trop utilisés ;

  • la bande passante actuelle est suffisante ;

  • le nombre de requêtes n'excède pas la capacité du serveur.

Configurer Sentry

Sentry est un tableau de bord qui vous permet de visualiser ce qui se passe dans l'application Django !

Il va bien au-delà des logs, vous fournissant des détails concernant le navigateur de l'utilisateur, l'URL... Et vous pouvez même le relier à GitHub pour suivre l'avancée des issues ! C'est beau, tout simplement.

Créez-vous un compte.

Puis suivez les instructions qui s'affichent à l'écran !

Commencez par choisir Django et créez un nouveau projet. Ensuite, revenez dans la console et installez la librairie Raven.

$ pip install raven

Enfin, mettez à jour le fichier de configuration de production :

settings/production.py

import raven

INSTALLED_APPS += [
    'raven.contrib.django.raven_compat',
]


RAVEN_CONFIG = {
    'dsn': 'https://somethingverylong@sentry.io/216272', # caution replace by your own!!
    # If you are using git, you can also automatically configure the
    # release based on the git info.
    'release': raven.fetch_git_sha(os.path.dirname(os.pardir)),
}

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'root': {
        'level': 'INFO', # WARNING by default. Change this to capture more than warnings.
        'handlers': ['sentry'],
    },
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s '
                      '%(process)d %(thread)d %(message)s'
        },
    },
    'handlers': {
        'sentry': {
            'level': 'INFO', # To capture more than ERROR, change to WARNING, INFO, etc.
            'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
            'tags': {'custom-tag': 'x'},
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose'
        }
    },
    'loggers': {
        'django.db.backends': {
            'level': 'ERROR',
            'handlers': ['console'],
            'propagate': False,
        },
        'raven': {
            'level': 'DEBUG',
            'handlers': ['console'],
            'propagate': False,
        },
        'sentry.errors': {
            'level': 'DEBUG',
            'handlers': ['console'],
            'propagate': False,
        },
    },
}

Voyons si ça fonctionne !

Nous allons volontairement ajouter une erreur dans notre code :

store/views.py

def index(request):
    MAIS POURQUOI EST-IL SI MECHANT ?
    # ...

Sauvez et relancez le serveur via Supervisor :

$ sudo supervisorctl restart disquaire-gunicorn

Allez sur la page qui liste les albums (/store), puis regardez dans Sentry si un bug a fait son apparition.

Hé oui !

Allez un peu plus loin et ajoutez le premier log de l'application. Nous allons envoyer un événement à Sentry lorsqu'une personne effectue une recherche.

views.py

import logging

# ...
# Get an instance of a logger
logger = logging.getLogger(__name__)

# ...

def search(request):
    #
    logger.info('New search', exc_info=True, extra={
        # Optionally pass a request and we'll grab any information we can
        'request': request,
    })
    return render(request, 'store/search.html', context)

Sauvez et relancez le serveur via Supervisor de nouveau :

$ sudo supervisorctl restart disquaire-gunicorn

Enfin, faites une recherche sur l'application en utilisant le formulaire de recherche. Vous constaterez qu'un nouvel événement "New search" vient de s'afficher dans le tableau de bord de Sentry !

Allez plus loin

Bravo, vous avez déployé une application Django comme un pro ! Vous pouvez être très fier·e de vous !

Source : CommitStrip

Le monde de l'Ops est très vaste et il vous reste beaucoup à découvrir. En effet, vous vous êtes concentré sur un seul serveur mais les grands projets ont souvent besoin d'une architecture plus robuste !

Les serveurs sont ainsi configurés pour servir l'app Django sans la base de données ou les fichiers statiques uniquement ! Découvrez-en plus dans cet article de CodingStartups.

Je vous invite également à lire le chapitre Deploying Django Projects du livre Two Scoops of Django.

Vous préférez écouter ? Découvrez cet épisode du Ruby Nouveau consacré aux personnes qui ne connaissent rien au Devops !

Enfin, allez plus loin en découvrant le déploiement continu dans cette vidéo.

À bientôt ! 👋

Exemple de certificat de réussite
Exemple de certificat de réussite