gwift-book/source/part-2-deployment/_main.adoc

151 lines
8.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

= Déploiement
[quote]
----
To be effective, a software system must be deployable.
The higher the cost of deployements, the less useful the system is.
A goal of a software architecture, then, should be to make a system that can be easily deployed with a single action.
Unfortunately, deployment strategy is seldom considered during initial development.
This leads to architectures that may be make the system easy to develop, but leave it very difficult to deploy.
-- Robert C. Martin, Clean Architecture, Chapitre 15, page 137
----
Il y a une raison très simple à aborder le déploiement dès maintenant: à trop attendre et à peaufiner son développement en local,
on en oublie que sa finalité sera de se retrouver exposé sur un serveur.
Il est du coup probable d'oublier une partie des désidérata, de zapper une fonctionnalité essentielle
ou simplement de passer énormément de temps à adapter les sources pour qu'elles fonctionnent sur un environnement en particulier.
Un bon déploiement ne doit pas dépendre de dizaines de petits scripts éparpillés sur le disque.
Lobjectif est qu'il soit rapide et fiable.
Ceci peut être atteint au travers dun partitionnement correct, incluant le fait que le composant principal sassure que
chaque sous-composant est correctement démarré intégré et supervisé.
Aborder le déploiement dès le début permet également de rédiger dès le début les procédures d'installation, de mises à jour et de sauvegardes.
Déploier une nouvelle version sera aussi simple que de récupérer la dernière archive depuis le dépôt, la placer dans le bon répertoire, appliquer des actions spécifiques (et souvent identiques entre deux versions), puis redémarrer les services adéquats, et la procédure complète se résumera à quelques lignes d'un script bash.
Le serveur que django met à notre disposition _via_ la commande `runserver` est extrêmement pratique, mais il est uniquement prévu pour la phase développement: en production, il est inutile de passer par du code Python pour charger des fichiers statiques (feuilles de style, fichiers JavaScript, images, ...).
De même, Django propose par défaut une base de données SQLite, qui fonctionne parfaitement dès lors que l'on connait ses limites et que l'on se limite à un utilisateur à la fois. En production, il est légitime que la base de donnée soit capable de supporter plusieurs utilisateurs et connexions simultanément...
En restant avec les paramètres par défaut, il est plus que probable que vous rencontriez rapidement des erreurs de verrou parce qu'un autre processus a déjà pris la main pour écrire ses données.
En bref, vous avez quelque chose qui fonctionne, mais qui ressemble de très loin à ce dont vous aurez besoin au final.
Dans cette partie, nous aborderons les points suivants:
* Définir l'infrastructure nécessaire à notre application et configurer l'hôte qui hébergera l'application: dans une machine physique, virtuelle ou dans un container. Nous aborderons aussi les déploiements via Ansible et Salt.
* Déployer notre code source
* Configurer les outils nécessaires à la bonne exécution de ce code et de ses fonctionnalités: les différentes méthodes de supervision de l'application, comment analyser les fichiers de logs, comment intercepter correctement une erreur si elle se présente et comment remonter l'information.
* Rendre notre application accessible depuis l'extérieur.
== Infrastructure & composants
Pour une mise ne production, le standard _de facto_ est le suivant:
* Nginx comme reverse proxy
* HAProxy pour la distribution de charge
* Gunicorn ou Uvicorn comme serveur d'application
* Supervisor pour le monitoring
* PostgreSQL ou MariaDB comme base de données.
* Celery et RabbitMQ pour l'exécution de tâches asynchrones
* Redis / Memcache pour la mise à en cache (et pour les sessions ? A vérifier).
Si nous schématisons l'infrastructure et le chemin parcouru par une requête, nous pourrions arriver à la synthèse suivante:
. L'utilisateur fait une requête via son navigateur (Firefox ou Chrome)
. Le navigateur envoie une requête http, sa version, un verbe (GET, POST, ...), un port et éventuellement du contenu
. Le firewall du serveur (Debian GNU/Linux, CentOS, ...) vérifie si la requête peut être prise en compte
. La requête est transmise à l'application qui écoute sur le port (probablement 80 ou 443; et _a priori_ Nginx)
. Elle est ensuite transmise par socket et est prise en compte par un des _workers_ (= un processus Python) instancié par Gunicorn. Si l'un de ces travailleurs venait à planter, il serait automatiquement réinstancié par Supervisord.
. Qui la transmet ensuite à l'un de ses _workers_ (= un processus Python).
. Après exécution, une réponse est renvoyée à l'utilisateur.
image::images/diagrams/architecture.png[]
=== Reverse proxy
Le principe du *proxy inverse* est de pouvoir rediriger du trafic entrant vers une application hébergée sur le système.
Il serait tout à fait possible de rendre notre application directement accessible depuis l'extérieur, mais le proxy a aussi l'intérêt de pouvoir élever la sécurité du serveur (SSL) et décharger le serveur applicatif grâce à un mécanisme de cache ou en compressant certains résultats footnote:[https://fr.wikipedia.org/wiki/Proxy_inverse]
=== Load balancer
=== Workers
=== Supervision des processus
=== Base de données
=== Tâches asynchrones
=== Mise en cache
== Code source
Au niveau logiciel (la partie mise en subrillance ci-dessus), la requête arrive dans les mains du processus Python, qui doit encore
. effectuer le routage des données,
. trouver la bonne fonction à exécuter,
. récupérer les données depuis la base de données,
. effectuer le rendu ou la conversion des données,
. et renvoyer une réponse à l'utilisateur.
Comme nous l'avons vu dans la première partie, Django est un framework complet, intégrant tous les mécanismes nécessaires à la bonne évolution d'une application.
Il est possible de démarrer petit, et de suivre l'évolution des besoins en fonction de la charge estimée ou ressentie, d'ajouter un mécanisme de mise en cache, des logiciels de suivi, ...
== Outils de supervision et de mise à disposition
== Méthode de déploiement
Nous allons détailler ci-dessous trois méthodes de déploiement:
* Sur une machine hôte, en embarquant tous les composants sur un même serveur. Ce ne sera pas idéal, puisqu'il ne sera pas possible de configurer un _load balancer_, de routeur plusieurs basées de données, mais ce sera le premier cas de figure.
* Dans des containers, avec Docker-Compose.
* Sur une *Plateforme en tant que Service* (ou plus simplement, *PaaS*), pour faire abstraction de toute la couche de configuration du serveur.
=== Sur une machine hôte
La première étape pour la configuration de notre hôte consiste à définir les utilisateurs et groupes de droits. Il est faut absolument éviter de faire tourner une application en tant qu'utilisateur *root*, car la moindre faille pourrait avoir des conséquences catastrophiques.
Une fois que ces utilisateurs seront configurés, nous pourrons passer à l'étape de configuration, qui consistera à:
1. Déployer les sources
2. Démarrer un serveur implémentant une interface WSGI (**Web Server Gateway Interface**), qui sera chargé de créer autant de [.line-through]#petits lutins# travailleurs que nous le désirerons.
3. Démarrer un superviseur, qui se chargera de veiller à la bonne santé de nos petits travailleurs, et en créer de nouveaux s'il le juge nécessaire
4. Configurer un proxy inverse, qui s'occupera d'envoyer les requêtes d'un utilisateur externe à la machine hôte vers notre serveur applicatif, qui la communiquera à l'un des travailleurs.
La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, ... Il existe des dizaines d'hébergements typés VPS (**Virtual Private Server**). A vous de choisir celui qui vous convient footnote:[Personnellement, j'ai un petit faible pour Hetzner Cloud].
include::debian.adoc[]
include::heroku.adoc[]
include::docker.adoc[]
WARNING: le serveur de déploiement ne doit avoir qu'un accès en lecture au dépôt source.
On peut aussi passer par fabric, ansible, chef ou puppet.
== Supervision
Qu'est-ce qu'on fait des logs après ? :-)
. Sentry via sentry_sdk
. Nagios
. LibreNMS
. Zabbix
Il existe également https://munin-monitoring.org[Munin], https://www.elastic.co[Logstash, ElasticSearch et Kibana (ELK-Stack)] ou https://www.fluentd.org[Fluentd].
== Autres outils
Voir aussi devpi, circus, uswgi, statsd.
See https://mattsegal.dev/nginx-django-reverse-proxy-config.html
== Ressources
* https://zestedesavoir.com/tutoriels/2213/deployer-une-application-django-en-production/
* https://docs.djangoproject.com/fr/3.0/howto/deployment/[Déploiement].
* Let's Encrypt !