Installer la Note Kfet en production

Cette page détaille comment installer la Note Kfet sur un serveur de production, dédié uniquement à l’utilisation de la note. On supposera que le serveur tourne avec un Debian Buster à jour.

Ajout des dépôts buster-backports

Debian c’est bien, c’est stable, mais les paquets sont vite obsolètes. En particulier, la version stable de Django dans la version stable de Debian est la version 1.11, qui n’est plus maintenue par Django depuis déjà quelques mois. Les versions stables de Django sont les versions 2.2 et 3.2, Debian Bullseye utilisant la version 2.2.

Afin de permettre à ses utilisateurs d’utiliser les dernières fonctionnalités de certains paquets, Debian a créé une distribution particulière, appelée buster-backports. Cette distribution contient des paquets de la distribution à venir « testing » (bullseye à l’heure où cette documentation est écrite) recompilés et réadapter pour fonctionner avec les paquets de la distribution stable (buster). Ce qui nous intéresse est de pouvoir récupérer Django 2.2 depuis cette distribution, et avoir donc une version maintenue de Django.

Plus de détails sur le wiki de Debian : https://wiki.debian.org/fr/Backports.

Pour activer les backports, il suffit d’ajouter dans le fichier /etc/apt/sources.list :

deb     $MIRROR/debian buster-backports main contrib

$MIRROR est votre miroir Debian favori, comme http://ftp.debian.org ou http://mirror.crans.org pour ce qui est de la version utilisée en production au BDE sur les serveurs du Crans. Il suffit ensuite de faire un sudo apt update. Vérifiez que les paquets sont bien récupérés, en cherchant cette ligne :

Get:4 http://mirror.crans.org/debian buster-backports InRelease [46.7 kB]

Avertissement

Avis aux futurs respos info : pensez à bien actualiser cette documentation lorsque Debian Bullseye sera sorti. En particulier, il ne sera pas déconnant de continuer à utiliser non pas buster-backports mais bullseye-backports pour installer la note avec Django 3.2 et non Django 2.2.

Bien sûr, vous testerez sur un serveur qui n’est pas celui utilisé avant :)

Installation des dépendances nécessaires

On s’efforce pour récupérer le plus possible de dépendances via les paquets Debian plutôt que via pip afin de faciliter les mises à jour et avoir une installation plus propre. On peut donc installer tout ce dont on a besoin, depuis buster-backports :

$ sudo apt update
$ sudo apt install -t buster-backports --no-install-recommends \
    gettext git ipython3 \  # Dépendances basiques
    fonts-font-awesome libjs-bootstrap4 \  # Pour l'affichage web
    python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
    python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \
    python3-djangorestframework python3-memcache python3-phonenumbers \
    python3-pil python3-pip python3-psycopg2 python3-setuptools python3-venv \
    texlive-xetex memcached

Ces paquets fournissent une bonne base sur laquelle travailler.

Pour les mettre à jour, il suffit de faire sudo apt update puis sudo apt upgrade.

Téléchargement de la note

Tout comme en développement, on utilise directement le Gitlab du Crans pour récupérer les sources.

On suppose que l’on veut cloner le projet dans le dossier /var/www/note_kfet.

On clone donc le dépôt en tant que www-data :

$ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git /var/www/note_kfet

Par défaut, le dépôt est configuré pour suivre la branche main, qui est la branche stable, notamment installée sur https://note.crans.org/. Pour changer de branche, notamment passer sur la branche beta sur un serveur de pré-production (un peu comme https://note-dev.crans.org/), on peut faire :

$ sudo -u www-data git checkout beta

Avertissement

Avis aux successeurs : notamment pour le serveur de production derrière https://note.crans.org, il peut être intéressant de créer un paquet Python de sorte à pouvoir installer la note en faisant directement pip install git+https://gitlab.crans.org/bde/nk20.git, et avoir ainsi une installation plus propre en décourageant le développement en production.

Voir par exemple comment le futur site d’inscription du Crans, Constellation, gère cela : https://gitlab.crans.org/nounous/constellation.

Installation des dépendances Python non présentes dans les dépôts APT

Même s’il est préférable d’installer les dépendances Python via les paquets APT, tous ne sont malheureusement pas disponibles.

On doit donc récupérer les dépendances manquantes via pip.

Tout comme en développement, on préfère avoir un environnement virtuel dédié, les sudo pip étant rarement compatibles avec les dépendances APT.

On construit donc un environnement virtuel et on installe les dépendances manquantes dans cet environnement :

$ cd /var/www/note_kfet
$ python3 -m venv env
$ . env/bin/activate
(env) $ pip install -r requirements.txt

Normalement, seules les dépendances manquantes sont installées, les autres sont trouvées globalement.

Plus d’informations sur les environnements virtuels dans la documentation officielle de Python : https://docs.python.org/fr/3/tutorial/venv.html.

Configuration de la note

La configuration de la note se gère essentiellement via des paramètres d’environnement. Ceux-ci sont lus via le fichier .env s’il existe, qui doit être placé à la racine du projet cloné (donc dans /var/www/note_kfet/.env). Un fichier d’exemple est situé dans le fichier .env_example, on peut donc faire un sudo cp .env_example .env.

Attention aux permissions : le fichier doit être lu par www-data et écrit (rien n’empêche de l’écrire en tant que root).

Le contenu de ce fichier :

DJANGO_APP_STAGE=prod
DJANGO_DEV_STORE_METHOD=sqlite
DJANGO_DB_HOST=localhost
DJANGO_DB_NAME=note_db
DJANGO_DB_USER=note
DJANGO_DB_PASSWORD=CHANGE_ME
DJANGO_DB_PORT=
DJANGO_SECRET_KEY=CHANGE_ME
DJANGO_SETTINGS_MODULE=note_kfet.settings
CONTACT_EMAIL=tresorerie.bde@localhost
NOTE_URL=localhost
NOTE_MAIL=notekfet@localhost
EMAIL_HOST=smtp.localhost
EMAIL_PORT=25
EMAIL_USER=notekfet@localhost
EMAIL_PASSWORD=CHANGE_ME
WIKI_USER=NoteKfet2020
WIKI_PASSWORD=

Le paramètre DJANGO_APP_STAGE accepte comme valeur dev ou prod. En développement, les mails ne sont pas envoyés mais affichés dans les logs du serveur. Les messages d’erreur sont directement affichés au lieu d’être envoyés par mail. Les paramètres d’envoi de mail n’ont donc aucun effet. En développement, il est également possible de choisir si l’on souhaite une base de données sqlite (par défaut) ou si on veut se connecter à une base de données PostgreSQL (rentrer postgres dans DJANGO_DEV_STORE_METHOD), auquel cas les paramètres de base de données seront interprétés.

Les champs DJANGO_DB_ sont relatifs à la connexion à la base de données PostgreSQL.

Le champ DJANGO_SECRET_KEY est utilisé pour la protection CSRF (voir la documentation https://docs.djangoproject.com/fr/3.2/ref/csrf/ pour plus de détails). Il s’agit d’une clé sous forme de chaîne de caractère suffisamment longue (64 caractères paraît bien) qui n’est pas à transmettre et qui évite d’autres sites malveillants de faire des requêtes directement sur la note.

Le champ CONTACT_EMAIL correspond l’adresse mail que les adhérent⋅e⋅s peuvent contacter en cas de problème. C’est là où le champ Nous contacter redirigera.

Le champ NOTE_URL correspond au nom de domaine autorisé à accéder au site. C’est également le nom de domaine qui sera utilisé dans l’envoi de mails pour générer des liens. En production, cela vaut note.crans.org.

Le champ NOTE_MAIL correspond au champ expéditeur des mails envoyés, que ce soit pour les rapports quotidiens / hebdomadaires / mensuels ou les mails d’erreur. En production, ce champ vaut notekfet2020@crans.org.

Les champs EMAIL_ sont relatifs à la connexion au serveur SMTP pour l’envoi de mails. En production, EMAIL_HOST vaut smtp.crans.org, EMAIL_PORT vaut 25 (on reste sur le réseau interne du Crans) et EMAIL_USER et EMAIL_PASSWORD sont vides (ce qui est valide car la note est sur le réseau du Crans, qui est déjà pré-autorisé à envoyer des mails).

Les champs WIKI_USER et WIKI_PASSWORD servent à s’authentifier sur le compte Wiki Crans NoteKfet2020, pour notamment exporter automatiquement la liste des activités sur le wiki.

Pour configurer la note, il est également possible de créer un fichier note_kfet/settings/secrets.py qui redéfinit certains paramètres, notamment la liste des administrateurs ou certaines applications optionnelles, ou encore certains éventuels mots de passe.

En production, ce fichier contient :

OPTIONAL_APPS = [
   'cas_server',
#    'debug_toolbar'
]

# When a server error occured, send an email to these addresses
ADMINS = (
    ('Note Kfet', 'notekfet2020@lists.crans.org'),
)

Configuration des tâches récurrentes

Certaines opérations se font périodiquement, comme le rappel hebdomadaire pour les personnes en négatif. On utilise pour cela un cron. Il suffit pour cela de copier le fichier note.cron vers /etc/cron.d/note, en veillant à ce qu’il appartienne bien à root.

Ce fichier contient l’ensemble des tâches récurrentes associées à la note. Une page de documentation dédiée fera bientôt son apparition.

Sur un serveur de pré-production, on peut ne pas souhaiter activer ces tâches récurrentes.

Installation de la base de données PostgreSQL

En production, on utilise une vraie base de données PostgreSQL et non un fichier sqlite. Beaucoup plus facile pour faire éventuellement des requêtes (bien que pas adapté pour Django) mais surtout bien mieux optimisé pour un serveur de production.

Pour installer la base de données, on commence par installer PostgreSQL :

$ sudo apt install --no-install-recommends postgresql postgresql-contrib

PostgreSQL est désormais installé et lancé. On crée un compte note, avec un bon mot de passe (le même que donné à Django) :

$ sudo -u postgres createuser -P note

Et on crée enfin une base de données nommée note_db appartenant à note.

$ sudo -u postgres createdb note_db -O note

La base de données est désormais prête à être utilisée.

Finir l’installation de Django

On commence par construire la base de données à partir des migrations enregistrées :

$ ./manage.py migrate

On doit compiler les traductions (pour pouvoir les lire plus vite par la suite) :

$ ./manage.py compilemessages

Les fichiers statiques (fiches de style, fichiers Javascript, images, …) doivent être exportées dans le dossier static :

$ ./manage.py collectstatic

Et on peut enfin importer certaines données de base :

$ ./manage.py loaddata initial

La note est désormais prête à être utilisée. Ne reste qu’à configurer un serveur Web.

Configuration de UWSGI

On dispose d’une instance de la note fonctionnelle et bien configurée. Cependant, nous n’avons pas encore de socket permettant d’intéragir avec le serveur. C’est le travail de UWSGI.

On rappelle que la commande ./manage.py runserver n’est pas conçue pour des serveurs de production, contrairement à UWSGI.

On commence par installer UWSGI :

$ sudo apt install --no-install-recommends uwsgi uwsgi-plugin-python3

On place ensuite le fichier de configuration UWSGI dans les applications installées. Un fichier de configuration est présent à la racine du projet, contenant :

[uwsgi]
uid             = www-data
gid             = www-data
# Django-related settings
# the base directory (full path)
chdir           = /var/www/note_kfet
# the virtualenv (full path)
home            = /var/www/note_kfet/env
wsgi-file       = /var/www/note_kfet/note_kfet/wsgi.py
plugin          = python3
# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = /var/www/note_kfet/note_kfet.sock
# ... with appropriate permissions - may be needed
chmod-socket    = 664
# clear environment on exit
vacuum          = true
# Touch reload
touch-reload    = /var/www/note_kfet/note_kfet/settings/__init__.py
# Enable threads
enable-threads  = true

Il suffit donc de créer le lien symbolique :

$ sudo ln -s /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/apps-enabled/uwsgi_note.ini

On peut désormais relancer UWSGI :

$ sudo systemctl restart uwsgi

Configuration de NGINX

Nous avons désormais un socket qui nous permet de faire des connexions au serveur web, placé dans /var/www/note_kfet/note_kfet.sock. Cependant, ce socket n’est pas accessible au reste du monde, et ne doit pas l’être : on veut un serveur Web Nginx qui s’occupe des connexions entrantes et qui peut servir de reverse-proxy, notamment utile pour desservir les fichiers statiques ou d’autres sites sur le même serveur.

On commence donc par installer Nginx :

$ sudo apt install nginx

On place ensuite dans /etc/nginx/sites-available/nginx_note.conf le fichier de configuration Nginx qui va bien, en remplaçant note.crans.org par ce qu’il faut :

# the upstream component nginx needs to connect to
upstream note {
    server unix:///var/www/note_kfet/note_kfet.sock; # file socket
}

# Redirect HTTP to nk20 HTTPS
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    location / {
        return 301 https://note.crans.org$request_uri;
    }
}

# Redirect all HTTPS to nk20 HTTPS
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    location / {
        return 301 https://note.crans.org$request_uri;
    }

    ssl_certificate /etc/letsencrypt/live/note.crans.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/note.crans.org/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

# configuration of the server
server {
    listen 443 ssl;
    listen [::]:443 ssl;

    # the port your site will be served on
    # the domain name it will serve for
    server_name note.crans.org;
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;

    # Django media
    location /media  {
        alias /var/www/note_kfet/media;
    }

    location /static {
        alias /var/www/note_kfet/static;
    }

    location /doc {
        alias /var/www/documentation;
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass note;
        include /etc/nginx/uwsgi_params;
    }

    ssl_certificate /etc/letsencrypt/live/note.crans.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/note.crans.org/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

On peut enfin activer le site :

$ sudo ln -s /etc/nginx/sites-available/nginx_note.conf /etc/nginx/sites-enabled/nginx_note.conf

Si on peut se dire que recharger Nginx suffira, il n’en est rien : voir paragraphe suivant.

Génération d’un certificat SSL

Nginx va essayer de lire les certificats présents dans /etc/letsencrypt/live/note.crans.org/, mais ce dossier n’existe pas encore.

On doit donc générer un certificat pour permettre les connexions HTTPS. Cela est permis grâce à certbot, qu’on s’empresse d’installer :

$ sudo apt install certbot python3-certbot-nginx

Le plugin pour nginx permet de certifier que le serveur a bien les droits pour note.crans.org grâce à Nginx, le BDE n’ayant a priori aucune raison de pouvoir gérer le nom de domaine crans.org.

On place dans le dossier /etc/letsencrypt/conf.d (qu’on crée au besoin) un fichier nommé nk20.ini :

# To generate the certificate, please use the following command
# certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly

# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 4096

# Always use the staging/testing server
# server = https://acme-staging.api.letsencrypt.org/directory

# Uncomment and update to register with the specified e-mail address
email = notekfet2020@lists.crans.org

# Uncomment to use a text interface instead of ncurses
text = True

# Use Nginx challenge
authenticator = nginx

En exécutant certbot, il va lire les fichiers de configuration Nginx et générer les certificats qu’il faut en créant un point d’entrée pour le serveur.

Il faut néanmoins que la configuration soit valide. Les certificats n’existant pas encore, la configuration nginx est donc pour l’instant invalide. Il faut alors temporairement commenter les parties de la configuration qui traitent des certificats et relancer nginx (sudo systemctl reload nginx).

On peut ensuite exécuter certbot :

$ certbot --config /etc/letsencrypt/conf.d/nk20.ini certonly

L’instruction certonly indique à certbot qu’il se contente de générer le certificat, sans chercher à l’installer. Si tout s’est bien passé, l’installation se fait simplement en décommentant les lignes préalablement commentées.

Un certificat généré de la sorte expire au bout de 3 mois. Néanmoins, certbot tourne régulièrement pour renouveler les certificats actifs automatiquement. Il n’y a donc plus rien à faire.

Après avoir rechargé la configuration de Nginx, rendez-vous sur https://note.crans.org (ou votre site) pour vérifier que tout fonctionne correctement :)

Mettre à jour la note

Pour mettre à jour la note, il suffit a priori, après avoir mis à jour les paquets APT, de faire un git pull dans le dossier /var/www/note_kfet.

Les éventuelles nouvelles migrations de la base de données doivent être appliquées :

$ ./manage.py migrate

Les nouvelles traductions compilées :

$ ./manage.py compilemessages

Les nouveaux fichiers statiques collectés :

$ ./manage.py collectstatic

Et enfin les nouvelles fixtures installées :

$ ./manage.py loaddata initial

Une fois tout cela fait, il suffit de relancer le serveur UWSGI :

$ sudo systemctl restart uwsgi

Avec Ansible

Tout ce travail peut sembler très laborieux et peut mériter d’être automatisé. Toutefois, il est essentiel de bien comprendre comment chaque étape de l’installation fonctionne.

Un playbook Ansible a été écrit permettant de réaliser toutes les tâches décrites ci-dessus. Il se trouve dans le dossier ansible.

Ansible s’installe sur votre propre machine (et non sur le serveur) en installant simplement le paquet ansible.

Pour déployer la note sur un serveur vierge, commencez par copier le fichier hosts_example en le nommant hosts. Ajoutez votre propre serveur, dans la section correspondante.

Dans le dossier host_vars, créez un fichier dont le nom est l’adresse du serveur, avec l’extension .yml.

Dans ce fichier, remplissez :

---
note:
  server_name: note.crans.org
  git_branch: main
  cron_enabled: true
  email: notekfet2020@lists.crans.org

en adaptant à votre configuration.

Il suffit ensuite de lancer ./base.yml -l urldevotreserveur.

Pour une première installation, vous devrez renseigner le mot de passe de la base de données pour créer le compte note. Vous devrez ensuite également refaire quelques ajustements pour générer le certificat, voir la partie certbot. La configuration du fichier .env sera également à faire à la main.

Cependant, pour mettre à jour, lancer cette commande suffit.

Copier une base de données

On peut vouloir périodiquement copier la base de données de production vers le serveur de développement, afin de travailler avec des données à jour.

On aura besoin de pouvoir accéder aux deux bases de données. On commence donc si ce n’est pas déjà fait par créer un utilisateur sur les deux serveurs :

ynerant@bde-note:~$ sudo -u postgres createuser -s ynerant

On réinitialise sur le serveur de développement la base de données présente, en éteignant tout d’abord le serveur Web :

ynerant@bde-note-dev:~$ sudo systemctl stop uwsgi
ynerant@bde-note-dev:~$ sudo -u postgres dropdb note_db
ynerant@bde-note-dev:~$ sudo -u postgres createdb -O note note_db

Et on copie enfin la base de données, en une seule ligne via SSH :

ynerant@bde-note:~$ pg_dump note_db | ssh note-dev.crans.org "psql note_db -f -"

On peut enfin redémarrer le serveur Web. Les données ont bien été copiées.

Prudence

On ne copiera jamais des données d’adhérent⋅e⋅s sur une machine personnelle. Ce type d’opération doit s’effectuer impérativement entre des serveurs du BDE.