Deploying a Django Application

2018-12-29 sysadmin-stuff debian deploy nginx

This one is in french to help fellow french-spekears

Vous avez codé un de vos premiers projets Django et vous êtes un peu perdu.e.s sur comment le déployer en pré-prod/prod ? Ne paniquez pas, voici une méthode qui a le mérite de marcher (on ne sait pas si c’est LA bonne méthode, mais on a réussi avec elle, plusieurs fois !)

Prérequis

  • vous êtes un utilisateur non root avec les privilèges sudo
  • votre projet contient un requirements.txt travaille avec un environnement virtuel
  • votre projet contient un settings.py et un localsettings.py
  • votre projet est versionné par git
  • pour plus de simplicité, on va supposer que votre projet utilise sqlite3 (qui est la base de données par défaut de Django) (et honnêtement, avec mysql/postgresql il suffit de créer la base de données et l’utilisateur, vous êtes grand.e.s on vous fait confiance)
  • on suppose que python3 et python3-dev sont installés
  • on suppose que nginx est installé
  • on suppose que vous avez accès au fichier de zone DNS et que vous avez ajouté votre sous-domaine subdomaine.ndd.tld au fichier de zone.

Si vous venez de le faire, la réplication DNS peut prendre du temps. En attendant, pensez à modifier votre /etc/hosts en y ajoutant une ligne de correspondance IP/sous-domaine :

xxx.xxx.xx.xx subdomaine.ndd.tld

Créer un nouvel utilisateur pour le projet

Pour être sûr.e.s que votre projet est bien enfermé là où il faut, nous vous proposons de créer un utilisateur particulier pour y mettre votre code.

Pour cela, tapez :

sudo adduser user-with-explicit-name --disabled-password

Puis, pour devenir, user-with-explicit-name :

sudo su - user-with-explicit-name

Puis, allez dans le home du nouvel utilisateur (cd).

Récupérer le code

Pour cela, il suffit de taper l’invocation git idoine :

git clone https://gitlab.ndd.tld/Username/my-awesome-project.git

Préparer l’environnement virtuel

Tout d’abord, entrez dans le dossier contenant le projet :

cd ~/my-awesome-project

Puis, créez l’environnement virtuel, que nous appelerons de façon très originale myvenv :

python3 -m venv myvenv

Et activez-le :

source myvenv/bin/activate

Enfin, installez vos dépendances (et gunicorn, si ce n’est pas dans vos dépendances de prod) :

pip install -r requirements.txt
pip install gunicorn

Modifications des sources Django

Modifications du localsettings.py

Il faut modifier le ALLOWED_HOSTS de la façon suivante :

ALLOWED_HOSTS = ['subdomaine.ndd.tld', 'domain2']

Modifications du settings.py

On peut mettre gunicorn dans les INSTALLED_APPS du setting.py.

Ouvrir les ports

iptables

Si vous utilisez IPTABLES, il faut taper quelque chose comme cela :

sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT<br />sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT

ferm (un petit aparté sur les firewall)

Si vous avez pas mal de règles IPTABLES à gérer, pas envie de créer un script qui les mets en place à chaque démarrage… il y a le formidable outil ferm !

Il permet de gérer très simplement ses règles IPTABLES via des fichiers et de générer plusieurs règles IPTABLES en une seule ligne. Il s’agit d’une surcouche d’IPTABLES et il possède sa propre syntaxe.

Installation

sudo apt install ferm

Attention, à l’installation, la configuration de firewall par défaut sera installé, pensez à sauvegarder vos règles IPTABLES quelque part.

Configuration

Le fichier de configuration se trouve dans /etc/ferm et se nomme ferm.conf.

Comme vous pouvez le voir dans ce fichier, il y a la table FILTER découpée en 3 parties :

  1. chain INPUT (qui concerne… les règles d’INPUT) ;
  2. chain OUTPUT (qui concerne… les règles d’OUTPUT) ;
  3. chain FORWARD (qui concerne… oui, vous avez bien deviné, les règles de FORWARD).

La politique de base pour INPUT et FORWARD est de tout dropper et la politique de base pour OUTPUT est de tout accepter.

Dans la section INPUT, on peut voir que sont ensuite autorisées :

  • les connexions déjà en cours (mod state state (ESTABLISHED RELATED) ACCEPT)
  • l’IPsec (proto udp dport 500 ACCEPT et proto (esp ah) ACCEPT)
  • le SSH (proto tcp dport ssh ACCEPT).

Si vous ne savez pas ce qu’est l’IPsec cela veut dire que vous n’en utilisez pas, vous pouvez donc supprimer les lignes concernant l’IPsec (ça ne sert jamais d’ouvrir des ports non utilisés en input et en forward à part prendre des risques inutiles :P)

Ensuite, c’est simple ! Pour ajouter vos règles vous les ajoutez dans la section concernée !

Par exemple, nous voulons ouvrir le port http (80) et https (443) en entrée (Input) sur le serveur, vu que nous sommes en train de parler d’une application web.

Il faut aller dans la section chain INPUT du fichier ferm.conf et ajouter :

proto tcp port http ACCEPT;<br />proto tcp port https ACCEPT;

Puis recharger ferm et c’est parti :

service ferm reload

Bonnes pratiques ferm (tant qu’on y est)

Le mieux, c’est d’écrire sur une seule et même ligne les choses qui sont similaires, par exemple :

proto scp dport (http https) ACCEPT;

De plus, ce qui peut être bien, pour éviter d’avoir un fichier ferm.conf à rallonge et ranger un peu toutes ces règles, c’est de séparer en répertoires nos configurations de firewall, et ce par thème (VPN, SSH, web, FTP, …).

C’est là qu’intervient la directive @include !

Une bonne pratique, c’est de laisser les règles de base (utile) de ferm dans ferm.conf à l’installation et ensuite d’ajouter la directive

@include "inpud.d"

à la fin de votre section chain INPUT.

Ensuite, créez un répertoire input.d dans /etc/ferm et ajoutez-y vos configurations.

Par exemple, pour vos configurations web, vous pouvez créez le fichier web.conf dans ce répertoire où vous ajouterez les règles que l’on a élaboré et puis, hop ! Un petit relaod du service et les règles sont mises en place.

Tout ce qui est possible avec IPTABLES est possible avec ferm. Il faut juste faire un peu de traduction niveau syntaxe, mais la doc est claire.

Tester la capacité de gunicorn à servir le projet

On suppose que vous avez appelé votre projet mysite.

/home/user-with-explicit-name/myvenv/bin/gunicorn --bind 0.0.0.0:8001 mysite.wsgi:application

Puis, connectez vous pour vérifier que tout se passe bien, c’est-à-dire visitez : http://domain_or_IP:8001

Service systemd

Écriture du fichier de configuration

Commencez par ouvrir (avec les bons privilèges)(et en tant que vous) le fichier systemd idoine :

sudo vim /etc/systemd/system/my-awesome-project.service

Puis, vous allez écrire les invocations suivantes :

[Unit]
Description="My awesome project"
After=syslog.target
After=network.target

[Service]
Type=simple
User=user-with-explicit-name
Environment=SECRET_KEY=secret
WorkingDirectory=/home/user-with-explicit-name/my-awesome-project/
ExecStart=/home/user-with-explicit-name/myvenv/bin/gunicorn/ --access-logfile - --workers 3 --bind
unix:/home/user-with-explicit-name/my-awesome-project/my-awesome-project.sock mysite.wsgi:application
TimeoutSec=300 

[Install]
WantedBy=multi-user.target
  • La partie [Unit] concerne les métadonnées et les dépendances. Ici, on lui dit de démarrer après syslog (pour les logs) et après que le réseau ait démarré (ce qui est assez raisonnable pour un site :p).
  • La partie [Service] concerne le service. On y spécifie l’utilisateur, le groupe, les modifications de l’environnement, le dossier dans lequel on travaille, le temps de timeout, mais surtout l’invocation de gunicorn.
  • La partie [Install] concerne ce qu’il se passe si le service est indiqué comme devant démarrer au démarrage du serveur. Ici, on lui dit de démarrer quand le système multi-utilisateur est chargé.

Démarrage du service

Vous pouvez vous lancer ! Il suffit d’invoquer cela :

sudo systemctl start my-awesome-project.service
sudo systemctl enable my-awesome-project.service

(Le enable sert à activer le service, c’est-à-dire à ce qu’il se lance systématiquement au démarrage.)

Vérification du statut du service

Vous pouvez vérifier l’état du service en utilisant la commande suivante :

sudo systemctl status my-awesome-project.service

Vous devriez avoir un truc qui ressemble à ça :

● my-awesome-project.service - "My awesome project"
    Loaded: loaded (/etc/systemd/system/my-awesome-project.service; enabled; vendor preset: enabled)
    Active: active (running) since Sun 2017-11-12 16:55:57 CET; 2 weeks 3 days ago

    Main PID: 15156 (gunicorn)
    Tasks: 2 (limit: 4915)
    Memory: 23.4M
    CPU: 3min 31.717s

    CGroup: /system.slice/my-awesome-project.service
    ├─15156 /home/user-with-explicit-name/myvenv/bin/python3 /home/user-with-explicit-name/myvenv/bin/gunicorn -access-logfile - --workers 3 --bind unix:/home/user-with-explicit-name/my-awesome-project/my-awesome-project.sock mysite.wsgi:application
    └─15159 /home/user-with-explicit-name/myvenv/bin/python3 /home/user-with-explicit-name/myvenv/bin/gunicorn -access-logfile - --workers 3 --bind unix:/home/user-with-explicit-name/my-awesome-project/my-awesome-project.sock mysite.wsgi:application

Installer certbot

Sous Debian 8 (Jessie)

Activer les backports

Ajouter cette ligne dans /etc/apt/source.list :

deb http://ftp.debian.org/debian jessie-backports main

Puis, faites un update.

Installation proprement dite

sudo apt-get install certbot -t jessie-backports

Si vous êtes sous Debian 9 (stretch)

Certbot est packagé, tout ce qu’il vous faut faire :

sudo apt install certbot

Certificats avec certbot

On présente une méthode, il en existe plusieurs (go go go go) !

La configuration de certbot

Il va falloir configurer un peu certbot pour nginx :

sudo certbot certonly --authenticator standalone --pre-hook "service nginx stop" <br />--post-hook "service nginx restart

La génération proprement dite

Il suffit de taper la commande suivante :

certbot certonly --standalone --preferred-challenges http subdomain.ndd.tld

Normalement vous devriez avoir plein de lignes qui vous disent où sont situés les fichiers importants pour la suite.

Le renouvellement des certificats

Comme les certificats Let’s Encrypt sont valides 90 jours, il paraît judicieux d’automatiser leur renouvellement ! Heureusement tout cela est pris en charge par certbot, via un cron.

Vérifiez que tout se passe bien en tapant :

sudo certbot renew --dry-run

Si vous voyez des erreurs, corrigez-les (mais tout devrait bien se passer !).

Dans tous les cas, il faut éditer la configuration pour recharger le service une fois les certificats renouvelés. Pour cela, il faut éditer le fichier /etc/letsencrypt/renewal/subdomain.ndd.tld.conf en ajoutant sur la dernière ligne :

renew_hook = systemctl reload my-awesome-project.service

De la conf nginx !

Maintenant que tout est prêt, il faut aller créer le fichier de configuration pour nginx dans /etc/nginx/sites-available/my-awesome-project !

Il faut y ajouter la configuration suivante :

server {

 listen 80;

 server_name subdomain.ndd.tld;

 server_tokens off;

 location / {

 return 301 https://subdomain.ndd.tld$request_uri;

 }

}

 

server {

 listen 443 ssl;
 listen [::]:443 ssl;
 server_name subdomain.ndd.tld;
 server_tokens off;
 access_log /var/log/nginx/my-awesome-project.access.log;
 error_log /var/log/nginx/my-awesome-project.error.log warn;

 ssl on;
 ssl_certificate /etc/letsencrypt/live/subdomain.ndd.tld/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/subdomain.ndd.tld/privkey.pem;
 client_max_body_size 20M;

 location = /favicon.ico { access_log off; log_not_found off; }

 location /static/ {
 root /home/user-with-explicit-name/my-awesome-project;
 }

 location / {
 include proxy_params;
 proxy_pass http://unix:/home/user-with-explicit-name/my-awesome-project/my-awesome-project.sock;
 }

}

Vous remarquerez qu’on utilise les certificats générés à l’étape précédente !

Superviser tout ça avec Icinga !

Il faut que vous supervisiez votre socket, pour voir les éventuels problèmes que vous avez sur votre site, et ce de façon plus pratique que de consulter votre site !

Fichiers de configuration Icinga

On ne va pas faire un cours complet sur comment déployer icinga, ni le configurer, ni mettre en place son interface web (vi o on sent bien que vous en avez marre :p…), on va juste se contenter de vous donner le fichier de configuration idoine :

define service{

 use generic-service
 host_name your-host-name
 service_description check_my_awesome_project
 check_command check_url_https!https://subdomain.domain.tld

}

Ensuite, il faut ajouter dans le fichier /etc/nagios-plugins/config/http.cfg les invocations suivantes :

define command{
  command_name check_url_https
  command_line /usr/lib/nagios/plugins/check_http --ssl -H '$HOSTADDRESS$' -u '$ARG1$'
}

Pfiou c’est fini.