Working on deployments processes

This commit is contained in:
Fred Pauchet 2024-02-27 21:51:11 +01:00
parent 8fa688c380
commit 910926b66e
9 changed files with 917 additions and 29 deletions

View File

@ -0,0 +1,24 @@
== Consider SQLite
Parfois, SQLite peut être une bonne option cite:[consider_sqlite] à envisager :
_Write througput is the area where SQLite struggles the most, but theres
not a ton of compelling data online about how it fares, so I got some of
my own: I spun up a Equinix m3.large.x86 instance, and ran a slightly
modified1 version of the SQLite kvtest2 program on it. Writing 512 byte
blobs as separate transactions, in WAL mode with synchronous=normal3,
temp_store=memory, and mmap enabled, I got 13.78latexmath:[$\mu$]s per
write, or ~72,568 writes per second. Going a bit larger, at 32kb writes,
I got 303.74latexmath:[$\mu$]s per write, or ~3,292 writes per second.
Thats not astronomical, but its certainly way more than most websites
being used by humans need. If you had 10 million daily active users,
each one could get more than 600 writes per day with that.
Looking at read throughput, SQLite can go pretty far: with the same test
above, I got a read throughput of ~496,770 reads/sec
(2.013latexmath:[$\mu$]s/read) for the 512 byte blob. Other people also
report similar results
— Expensify reports that you can get 4M QPS if youre willing to make
some slightly more involved changes and use a beefier server. Four
million QPS is enough that every internet user in the world could make
~70 queries per day, with a little headroom left over. Most websites
dont need that kind of throughput._

View File

@ -1 +1,31 @@
== Cloud-native
=== Docker
[quote]
====
Docker […] helps software applications run inside containers (Containers provide a clean, tidy environment for software applications that make them easier to run anywhere).
Docker started as an internal project within dotCloud, a platform-as-a-service, but became so popular that the founders decided to make Docker the main focus of the company.
The Docker project was open sourced in 2013. Docker has raised $180M with an estimated valuation of over $1B. Their business is based on support, private plans, and services. Dockers 2014 revenue was less than $10 million cite:[roads_and_bridges(48)].
====
=== Docker-compose
Dabord, après avoir installer docker-compose et les dépendances sous debian, tu dois tajouter dans le groupe docker, sinon il faut être root pour utiliser docker.
Bon après cest facile, un petit virtualenv pour cookiecutter, suivi dune installation du template django. Et puis jai suivi https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html
Alors, il télécharge les images, fait un petit update, installe les dépendances de dev, install les requirement pip …
Du coup, ça prend vite de la place : image.png
Limage de base python passe de 179 à 740 MB.
Et là jen ai pour presque 1,5 GB dun coup.
Mais par contre, jai un python 3.7 direct et postgres 10 sans rien faire ou presque.
La partie ci-dessous a été reprise telle quelle de https://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html[la documentation de cookie-cutter-django].
le serveur de déploiement ne doit avoir quun accès en lecture au dépôt source.
=== Portainer

View File

@ -1 +0,0 @@
== Docker & Portainer

View File

@ -1 +1,576 @@
== Native
== Debian/Ubuntu
image::deployment/debian-ubuntu.png[align="center"]
Le déploiement sur Debian est présenté dans le cadre dun déploiment *on-premises* ou *IaaS*.
Nous verrons trois types de déploiements:
. *Manuel*, en exécutant chaque étape une par une. Ce type de déploiement est intéressant à réaliser au moins une fois, afin de comprendre les différents composants utilisés.
. *Semi-automatisé*, _via_ Ansible. Cette manière se base sur ce qui aura été présenté à la première section, tout en faciliant certains aspects. Plusieurs concepts devront cependant être abordés au préalable, ce qui pourra paraitre rebutant en première instance.
. *Automatisé* en container, qui reste un déploiement _on-premise_, mais qui prépare déjà le chemin pour les containeurs. Ce mode de déploiement-ci garde quelques contraintes, mais présente également quelques avantages.
Par soucis de simplification, quelques raccourcis seront pris :
* La base de données sera hébergée sur la même machine que lapplication,
* Il ny aura pas de _load balancer_.
[cols="<,<",options="header",]
|===
|Avantages |Désavantages
|*Une grande accessibilité*: un VPS ne coûte que quelques euros par mois et permet de mettre un projet à disposition de plusieurs utilisateurs |Nécessite du temps et de linvestissement personnel
| |Nécessite de suivre les évolutions et de prendre une petite journée par an pour appliquer les dernières mises à jour
| |Nécessite de sécuriser soi-même son infrastructure (clés SSH, Fail2ban, ...)
| |Le montage final ressemble un peu à de lartisanat (bien quAnsible "professionalise" sérieusement le tout)
|===
Le choix de ces deux systèmes dexploitation sexplique par une grande liberté, une bonne compatibilité avec différentes architectures, une bonne stabilité générale et une documentation suffisante:
* Debian fonctionne sur un mécanisme de https://www.debian.org/releases/[canaux]: stable, testing et unstable. Globalement, en choisissant le premier canal, vous aurez des paquets _très_ stables et des procédures de sauts de versions correctement documentées.
* Ubuntu fonctionne grâce à des versions _Long-Term Support_ , supportées durant cinq ans et sortant tous les deux ans. En pratique, en installant une version 22.04, celle-ci sera supportée jusquen avril 2027, ce qui vous laissera le temps de planifier le _downtime_. Entre chacun de ces versions LTS, des versions intermédiaires sont mises à
dispositions tous les six mois, mais ne sont supportées que durant 9 mois. Pour cette raison, leur utilisation sur un serveur en production est fortement déconseillée, car demanderait une maintenance récurrente et trop fréquente.
Le processus de configuration à :
. Initialiser le système : configuration SSHd, _firewall_, installation d'un moteur de base de données, ...
. Déployer ou mettre les sources à disposition,
. Démarrer un service implémentant une interface WSGI (*Web Server Gateway Interface*) , qui sera chargé de créer autant de petits lutins travailleurs que nous le désirerons (_Gunicorn_),
. Démarrer un superviseur, qui se chargera de veiller à la bonne santé de nos petits travailleurs, et en créer de nouveaux sil le juge nécessaire (_Supervisord_),
. Configurer un proxy inverse, qui soccupera denvoyer les requêtes dun utilisateur externe à la machine hôte vers notre serveur applicatif, qui la communiquera à lun des travailleurs, pour exécution (_Nginx, Apache, ...).
La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, ...
Il existe des dizaines dhébergements typés VPS (*Virtual Private Server*).
A vous de choisir celui qui vous convient footnote:[Personnellement, jai un petit faible pour Hetzner Cloud, notamment pour les fonctionnalités qu'ils proposent au niveau du roulement des sauvegardes, au niveau de la sécurité d'accès à la console grâce à du MFA, ...].
=== Initialisation du serveur
Nous allons commencer par initialiser le système, configurer les droits utilisateurs, installer une version de linterpréteur Python et configurer les dépendances principales.
==== Configuration des droits utilisateurs
La toute première étape pour la configuration de notre hôte consiste à définir les utilisateurs et groupes de droits.
[DANGER]
====
Faire en sorte que chaque application tourne dans son propre espace de privilèges est un principe de sécurité fondamental.
Il évite qu'on ne se retrouve avec une application démarrée en tant que *root* - la moindre faille pourrait avoir des conséquences catastrophiques.
Si votre application comprenait une faille qui permettrait (par exemple) de supprimer un fichier lambda dont le chemin serait passé en paramètre, le fait dexécuter notre application sous son propre utilisateur empêche au moins que des fichiers hors de son périmètre ne soit supprimés - ce qui ne serait pas le cas si l'application tournait en tant que *root*.
====
Dans lordre, nous devons réaliser les étapes suivantes :
* Ajouter un nouveau groupe système, intitulé `webapps`, qui servira à partager des fichiers entre les différents composants. Lutilisateur qui fait tourner le proxy inverse sera également ajouté à ce groupe, un peu plus tard.
* Ajouter un groupe qui servira à gérer la communications _via_ https://fr.wikipedia.org/wiki/Berkeley_sockets[sockets], qui consiste en un ensemble normalisé de fonctions de communication entre processus,
* Créer un utilisateur application, afin de le conserver isolé du reste du système,
* Les applications seront placées dans le répertoire `/home/gwift`,
* Octroi des droits de notre utilisateur `+gwift+` sur son propre répertoire `+/home/gwift+`.
Pour résumer, lensemble de ces commandes nous donne ceci:
[source,bash]
----
groupadd system webapps
groupadd system gunicorn_sockets
useradd system gid webapps shell /bin/bash home /home/gwift gwift
mkdir -p /home/gwift
chown gwift:webapps /home/gwift
----
==== Dépendances systèmes
Debian et Ubuntu comprennent nativement une version récente de Python 3.
Mettez vos dépôts à jour et installez la avec les commandes suivantes :
[source,bash]
----
apt update
apt install python3
----
Si vous souhaitez utiliser une version ultérieure, il suffit de linstaller *en parallèle* de la version officiellement supportée par votre distribution, soit en utilisant pyenv (comme nous l'avons déjà vu), soit de la manière suivante :
[source,bash]
----
apt install openssl-devel bzip2-devel libffi-devel
wget https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tgz
cd Python-3.8*/
./configure --enable-optimizations
make altinstall <1>
----
<1> *Attention !* Le paramètre `altinstall` est primordial. Sans lui, vous écraserez linterpréteur initialement supporté par la distribution, et cela pourrait avoir des effets de bord non souhaités.
==== Base de données
On la déjà vu, Django se base sur un pattern type https://www.martinfowler.com/eaaCatalog/activeRecord.html[ActiveRecords] pour la gestion de la persistance des données et supporte les principaux moteurs de bases de données relationnelles connus :
* SQLite (en natif),
* MariaDB / MySQL (en natif depuis Django 3.0),
* PostgreSQL au travers de `psycopg2` (en natif aussi),
* Microsoft SQLServer grâce aux drivers https://github.com/microsoft/mssql-django[Microsoft]
* Oracle via https://oracle.github.io/python-cx_Oracle/[cx_Oracle].
Chaque pilote doit être utilisé précautionneusement !
Chaque version de Django nest pas toujours compatible avec chacune des versions des pilotes, et chaque moteur de base de données nécessite parfois une version spécifique du pilote.
Par ce fait, vous serez parfois bloqué sur une version de Django, simplement parce que votre serveur de base de données se trouvera dans une version spécifique (eg. Django 2.3 à cause dun Oracle 12.1).
Ci-dessous, nous détaillerons l'installation d'une base PostgreSQL sur notre serveur, afin de disposer de notre propre instance.
A quelques commandes près, cette procédure peut être réutilisée telle quelle pour un autre moteur.
Les autres moteurs présentent soit des problèmes liées au coût des licences (Oracle, MSSQL), soit de limitations dues au nombre d'utilisateurs concurrents (SQLite - bien que ... cite:[consider_sqlite]).
....
apt install postgresql postgresql-contrib
....
Ensuite, nous créerons un utilisateur pour la base de données de notre application. De la même manière que pour lutilisateur système, il nest pas acceptable que la chaine de connexion au moteur de base de données soient associées à un compte administrateur :
[source,bash]
----
# su - postgres
postgres@gwift:~$ createuser --interactive -P
Enter name of role to add: gwift_user
Enter password for new role:
Enter it again:
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n
----
Finalement, nous pouvons effectivemment créer la base de données qui
hébergera les données:
[source,bash]
----
postgres@gwift:~$ createdb --owner gwift_user gwift
postgres@gwift:~$ exit
logout
----
[NOTE]
====
[cols="<,<,<",options="header",]
|===
|Etape |PostgreSQL |MariaDB
|Installation |`+apt install postgresql postgresql-contrib+`
|`+apt install maria-db+`
|Création de lutilisateur | |
|Création de la base de données | |
|Dump | |
|===
====
=== Préparation de lenvironment utilisateur
[source,bash]
----
su - gwift
cp /etc/skel/.bashrc .
cp /etc/skel/.bash_profile .
ssh-keygen
mkdir bin
mkdir .venvs
mkdir webapps
python3.6 -m venv .venvs/gwift
source .venvs/gwift/bin/activate
cd /home/gwift/webapps
----
[source,bash]
----
# en tant qu'utilisateur 'gwift'
source .venvs/gwift/bin/activate
pip install -U pip
pip install -r requirements/base.txt
pip install gunicorn
cd webapps/gwift
gunicorn config.wsgi:application --bind localhost:3000 --settings=config.settings_production
----
==== Préparation des fichiers statiques
[source]
----
mkdir -p /var/www/gwift/static
----
==== Préparation des fichiers media
TBC
==== Préparation du socket de communication
Dans le fichier `+/etc/tmpfiles.d/gwift.conf+`:
....
D /var/run/webapps 0775 gwift gunicorn_sockets -
....
Suivi de la création par systemd :
....
systemd-tmpfiles --create
....
==== Configuration de lapplication
Comme nous l'avons vu dans les 12 facteurs, la configuration de l'environnement est réalisée à partir d'un fichier `.env` que nous placerons dans le répertoire utilisateur.
Ce fichier `.env` contiendra _a minima_ les clés de configuration suivante :
[source]
----
SECRET_KEY=<set your secret key here> <1>
ALLOWED_HOSTS=*
STATIC_ROOT=/var/www/gwift/static
DATABASE=... <2>
----
<1> Utilisée pour le chiffrement des sessions,
<2> Nous ferons confiance à `django_environ` pour traduire la chaîne de connexion à la base de données.
==== Création des répertoires de logs
==== Gunicorn
[source]
----
#!/bin/bash
NAME="gwift"
DJANGODIR=/home/gwift/webapps/gwift
SOCKFILE=/var/run/webapps/gunicorn_gwift.sock
USER=gwift
GROUP=gunicorn_sockets
NUM_WORKERS=5 <1>
DJANGO_SETTINGS_MODULE=config.settings_production
DJANGO_WSGI_MODULE=config.wsgi
echo "Starting $NAME as `whoami`"
source /home/gwift/.venvs/gwift/bin/activate
cd $DJANGODIR
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--user $USER \
--bind=unix:$SOCKFILE \
--log-level=debug \
--log-file=-
----
<1> Ce paramètre correspond généralement au nombre de CPUs présents sur la machine, auquel nous ajoutons 1.
=== Composants périphériques
==== Supervsion, keepalive et autoreload
Pour la supervision, nous avons deux choix principaux - bien qu'il existe d'autres solutions, que nous ne détaillerons pas ici - :
* Soit utiliser SystemD, qui est présent sur la majorité des distributions du marché - sauf exceptions (https://www.gentoo.org[Gentoo], https://www.devuan.org/[Devuan], les https://en.wikipedia.org/wiki/Comparison_of_BSD_operating_systems[*BSD], ...),
* Soit passer par http://supervisord.org[Supervisord].
[NOTE]
====
Comme indiqué, il existe d'autres superviseurs, comme https://circus.readthedocs.io/en/latest/[Circus] ou https://uwsgi-docs.readthedocs.io/en/latest/[uWSGI].
Ils fonctionnent tous un peu de la même manière.
Choisissez celui qui vous correspond le mieux.
====
===== Systemd
[source]
----
# /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn/socket <1>
[Install]
WantedBy=sockets.target
----
<1> Ce paramètre pourra être réutilisé dans le démarrage de notre _worker_
[source]
----
# /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
PIDFile=/run/gunicorn/pid
User=
Group=www-data
RuntimeDirectory=gunicorn
WorkingDirectory=/home/chengue/sites/example/example
ExecStart=/home/chengue/.virtualenvs/example/bin/gunicorn \
--access-logfile /home/chengue/sites/logs/example.access.log \
--error-logfile /home/chengue/sites/logs/example.error.log \
--pid /run/gunicorn/pid \
--env DJANGO_SETTINGS_MODULE=example.settings.production \
--bind unix:/run/gunicorn/socket example.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
----
===== Supervisord
[source]
----
apt install supervisor -y
----
On crée ensuite le fichier `/etc/supervisord.d/gwift.ini`:
[source]
----
[program:gwift]
command=/home/gwift/bin/start_gunicorn.sh
user=gwift
stdout_logfile=/var/log/gwift/gwift.log
autostart=true
autorestart=unexpected
redirect_stdout=true
redirect_stderr=true
----
Et on crée les répertoires de logs, on démarre supervisord et on vérifie quil tourne correctement :
....
$ mkdir /var/log/gwift
$ chown gwift:nagios /var/log/gwift
$ systemctl enable supervisord
$ systemctl start supervisord.service
$ systemctl status supervisord.service
supervisord.service - Process Monitoring and Control Daemon
Loaded: loaded (/usr/lib/systemd/system/supervisord.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2019-12-24 10:08:09 CET; 10s ago
Process: 2304 ExecStart=/usr/bin/supervisord -c /etc/supervisord.conf (code
=exited, status=0/SUCCESS)
Main PID: 2310 (supervisord)
CGroup: /system.slice/supervisord.service
- 2310 /usr/bin/python /usr/bin/supervisord -c
/etc/supervisord.conf
- 2313 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2317 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2318 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2321 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2322 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
- 2323 /home/gwift/.venvs/gwift/bin/python3
/home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...
ls /var/run/webapps
....
On peut aussi vérifier que lapplication est en train de tourner, à laide de la commande `supervisorctl` :
....
$ supervisorctl status gwift
gwift RUNNING pid 31983, uptime 0:01:00
supervisorctl stop gwift
gwift: stopped
root@ks3353535:/etc/supervisor/conf.d# supervisorctl start gwift
gwift: started
root@ks3353535:/etc/supervisor/conf.d# supervisorctl restart gwift
gwift: stopped
gwift: started
....
==== Firewall
[source]
----
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
----
* On ouvre le port 80, uniquement pour autoriser une connexion HTTP, mais qui sera immédiatement redirigée vers HTTPS
* Et le port 443 (forcément).
==== Reverse proxy
....
apt install nginx -y
usermod -a -G gunicorn_sockets nginx
....
On configure ensuite le fichier `+/etc/nginx/conf.d/gwift.conf+`:
....
apt install nginx -y
usermod -a -G gunicorn_sockets nginx
....
On configure ensuite le fichier `+/etc/nginx/conf.d/gwift.conf+`:
....
upstream gwift_app {
server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0;
}
server {
listen 80;
server_name <server_name>;
root /var/www/gwift;
error_log /var/log/nginx/gwift_error.log;
access_log /var/log/nginx/gwift_access.log;
client_max_body_size 4G;
keepalive_timeout 5;
gzip on;
gzip_comp_level 7;
gzip_proxied any;
gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
location /static/ {
access_log off;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
add_header Vary "Accept-Encoding";
try_files $uri $uri/ =404;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
client_max_body_size 4G;
keepalive_timeout 5;
gzip on;
gzip_comp_level 7;
gzip_proxied any;
gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
location /static/ {
access_log off;
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
add_header Vary "Accept-Encoding";
try_files $uri $uri/ =404;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://gwift_app;
}
}
}
....
* Ce répertoire sera complété par la commande `+collectstatic+` que lon
verra plus tard. Lobjectif est que les fichiers ne demandant aucune
intelligence soit directement servis par Nginx. Cela évite davoir un
processus Python (relativement lent) qui doive être instancié pour
servir un simple fichier statique.
* Afin déviter que Django ne reçoive uniquement des requêtes provenant de 127.0.0.1
===== Lets Encrypt
Certificats externes + communication par socket interne.
=== Mise à jour
....
u - <user>
source ~/.venvs/<app>/bin/activate
cd ~/webapps/<app>
git fetch
git checkout vX.Y.Z
pip install -U requirements/prod.txt
python manage.py migrate
python manage.py collectstatic
kill -HUP `ps -C gunicorn fch -o pid | head -n 1`
....
* https://stackoverflow.com/questions/26902930/how-do-i-restart-gunicorn-hup-i-dont-know-masterpid-or-location-of-pid-file
==== Logrotate
....
/var/log/gwift/* {
weekly
rotate 3
size 10M
compress
delaycompress
}
....
Puis on démarre logrotate avec `+logrotate -d /etc/logrotate.d/gwift+`
pour vérifier que cela fonctionne correctement.
==== Sauvegardes
Les sauvegardes ont été configurées avec borg :
`+yum install borgbackup+`.
Cest lutilisateur gwift qui sen occupe.
....
mkdir -p /home/gwift/borg-backups/
cd /home/gwift/borg-backups/
borg init gwift.borg -e=none
borg create gwift.borg::{now} ~/bin ~/webapps
....
Et dans le fichier crontab :
....
0 23 * * * /home/gwift/bin/backup.sh
....
==== Check list
.
=== Ansible
On peut aussi passer par fabric, ansible, chef ou puppet.
==== Conclusions
Ce type de déploiement est complexe lors dune première fois, mais est relativement rapide par la suite.
Comptez une heure ou deux lorsque vous aurez pris lhabitude.
Il reste cependant assez rébarbatif, même après en avoir pris l'habitude : il faut penser à beaucoup de points spécifiques (configuration des droits, paramétrage des répertoires, accès croisés entre le _reverse proxy_ et le côté applicatif, s'assurer d'avoir une procédure de recouvrement des données en cas de plantage, ...).
Nous pouvons aussi constater le manque de résilience : si le serveur venait à planter pour n'importe quelle raison, l'application serait rendue totalement inaccessible.
De même, si vous veniez à devoir migrer votre serveur vers une machine plus puissante ou offrant plus d'espace disque, vous seriez contraint à devoir migrer l'ensemble des fichiers, y compris les média uploadés par vos utilisateurs.
En conclusion, cette manière de faire est correcte.
Elle présente quelques avantages (tout est sur la machine, accessible et sans dépendances), mais aussi plusieurs désavantages principalement liés à la résilience.

285
book/deployment/paas.adoc Normal file
View File

@ -0,0 +1,285 @@
== _Platform as a Service_
Les _Plateform As A Service_ , où vous choisissez le _service ou le composant_ dont vous avez besoin (une base de données, un service de cache, un service applicatif, ...), vous lui
envoyer les paramètres nécessaires et le tout démarre gentiment sans que vous ne deviez superviser lhôte.
En pratique, ce mode démarrage se conforme aux 12 facteurs dont nous avons déjà parlé plus tôt - modifier un paramètre reviendra à couper l'instance actuelle pour en déployer une nouvelle.
[NOTE]
====
Dans la suite, nous prendrons Heroku, mais il existe dautres plateformes similaires, notamment https://www.pythonanywhere.com/details/django_hosting[PythonAnywhere] ou https://fly.io/[Fly.io].
Le fonctionnement de ces différentes plateformes est généralement similaire (si pas identique...), et consiste généralement à respecter certaines conventions de configuration et à suivre certaines étapes spécifiques pour la mise à disposition.
====
=== Heroku
Pour un projet de type "hobby" et pour lexemple de déploiement ci-dessous, il est tout à fait possible de sen sortir sans dépenser un kopek, afin de tester nos quelques idées ou mettre rapidement un _Most Valuable Product_ en place.
La seule contrainte consistera à pouvoir héberger des fichiers envoyés par vos utilisateurs - ceci pourra être
fait en configurant un _bucket compatible S3_, par exemple chez Amazon, Scaleway ou OVH.
[DANGER]
====
Par la suite, il conviendra dinvestir un minimum : sur le _tiers_ par défaut, des bases de données sont disponibles, mais montrent rapidement leurs limites.
Les 10 000 enregistrements "_offerts_" sont une limite *très* rapidement atteinte, tandis quaucun mécanisme de migration nest prévu.
Ceci signifie quaprès avoir démarré un projet en mode "hobby", il vous sera nécessaire de gérer vous-même la migration de vos données, dans la mesure où aucun processus automatique nexiste entre instances de deux niveaux différents.
====
Le fonctionnement est relativement simple : pour chaque application, Heroku crée un dépôt Git qui lui est associé.
Il suffit donc denvoyer les sources de votre application vers ce dépôt pour quHeroku les interprête comme étant une nouvelle version, déploie les nouvelles fonctionnalités - sous réserve que tous les tests passent correctement - et les mettent à disposition.
Dans un fonctionnement plutôt manuel, chaque déploiement est initié par le développeur ou par un membre de léquipe. Dans une version plus automatisée, chacun de ces déploiements peut être placé en fin de _pipeline_, lorsque tous les tests unitaires et dintégration auront été réalisés.
Au travers de la commande `heroku create`, vous associez donc une nouvelle référence à votre code source, comme le montre le contenu du fichier `.git/config` ci-dessous :
[source]
----
$ heroku create
Creating app... done, -> young-temple-86098
https://young-temple-86098.herokuapp.com/ | https://git.heroku.com/young-
temple-86098.git
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[remote "heroku"]
url = https://git.heroku.com/still-thicket-66406.git
fetch = +refs/heads/*:refs/remotes/heroku/*
----
Pour définir de quel type d'application il s'agit, Heroku nécessite un minimum de configuration.
Celle-ci se limite aux deux fichiers suivants :
* Déclarer un fichier `Procfile` qui va simplement décrire le fichier à passer au protocole WSGI
* Déclarer un fichier `requirements.txt` (qui va éventuellement chercher ses propres dépendances dans un sous-répertoire, avec l'option `-r`)
Après ce paramétrage, il suffit de pousser les changements vers ce nouveau dépôt grâce à la commande `+git push heroku main+`.
[WARNING]
====
Puisque nous avons précedemment proposé dutiliser Poetry, la commande `+export+` pourra être utilisée pour générer le fichier `+requirements.txt+`, comme exigé par Heroku.
====
Heroku propose des espaces de déploiements, mais pas despace de
stockage. Il est possible dy envoyer des fichiers utilisateurs
(typiquement, des media personnalisés), mais ceux-ci seront perdus lors
du redémarrage du container. Il est donc primordial de configurer
correctement lhébergement des fichiers média, de préférences sur un
stockage compatible S3. s3
Prêt à vous lancer ? Commencez par créer un compte :
https://signup.heroku.com/python.
=== Configuration du compte
+ Récupération des valeurs denvironnement pour les réutiliser
ci-dessous.
Vous aurez peut-être besoin dun coup de pouce pour démarrer votre
première application; heureusement, la documentation est super bien
faite:
.Heroku: Commencer à travailler avec un langage
image::images/deployment/heroku-new-app.png[images/deployment/heroku-new-app]
Installez ensuite la CLI (_Command Line Interface_) en suivant
https://devcenter.heroku.com/articles/heroku-cli[la documentation
suivante].
Au besoin, cette CLI existe pour :
. macOS, _via_ brew
. Windows, grâce à un
https://cli-assets.heroku.com/heroku-x64.exe[binaire x64] (la version 32
bits existe aussi, mais il est peu probable que vous en ayez besoin)
. GNU/Linux, via un script Shell
`+curl https://cli-assets.heroku.com/install.sh  sh+` ou sur
https://snapcraft.io/heroku[SnapCraft].
Une fois installée, connectez-vous :
....
$ heroku login
....
Et créer votre application :
....
$ heroku create
Creating app... done, -> young-temple-86098
https://young-temple-86098.herokuapp.com/ | https://git.heroku.com/young-
temple-86098.git
....
.Notre application est à présent configurée!
image::images/deployment/heroku-app-created.png[images/deployment/heroku-app-created]
Ajoutons lui une base de données, que nous sauvegarderons à intervalle
régulier :
....
$ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on -> still-thicket-66406... free
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-clear-39693 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
$ heroku pg:backups schedule --at '14:00 Europe/Brussels' DATABASE_URL
Scheduling automatic daily backups of postgresql-clear-39693 at 14:00
Europe/Brussels... done
....
(+ voir comment récupérer les backups)
....
# Copié/collé de https://cookiecutter- django.readthedocs.io/en/latest/deployment-on-heroku.html
heroku create --buildpack https://github.com/heroku/heroku-buildpack-python
heroku addons:create heroku-redis:hobby-dev
heroku addons:create mailgun:starter
heroku config:set PYTHONHASHSEED=random
heroku config:set WEB_CONCURRENCY=4
heroku config:set DJANGO_DEBUG=False
heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production
heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)"
# Generating a 32 character-long random string without any of the visually
similar characters "IOl01":
heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A-
HJ-NP-Za-km-z2-9' | head -c 32)/"
# Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com'
heroku config:set DJANGO_ALLOWED_HOSTS=
# Assign with AWS_ACCESS_KEY_ID
heroku config:set DJANGO_AWS_ACCESS_KEY_ID=
# Assign with AWS_SECRET_ACCESS_KEY
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=
# Assign with AWS_STORAGE_BUCKET_NAME
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=
git push heroku main
heroku run python manage.py createsuperuser
heroku run python manage.py check --deploy
heroku open
....
=== Type dapplication
Pour quHeroku comprenne le type dapplication à démarrer, ainsi que les
commandes à exécuter pour que tout fonctionne correctement. Pour un
projet Django, cela comprend, à placer à la racine de votre projet :
. Un fichier `+requirements.txt+` (qui peut éventuellement faire appel à
un autre fichier, *via* largument `+-r+`)
. Un fichier `+Procfile+` ([sans
extension](https://devcenter.heroku.com/articles/procfile)!), qui
expliquera la commande pour le protocole WSGI.
....
# Procfile
release: python3 manage.py migrate
web: gunicorn gwift.wsg
....
=== Hébergement S3
....
# requirements.txt
django==3.2.8
gunicorn
boto3
django-storages
....
Pour cette partie, nous allons nous baser sur
lhttps://www.scaleway.com/en/object-storage/[Object Storage de
Scaleway]. Ils offrent 75GB de stockage et de transfert par mois, ce qui
va nous laisser suffisament despace pour jouer un peu.
image:images/deployment/scaleway-object-storage-bucket.png[image]
Lidée est quau moment de la construction des fichiers statiques,
Django aille simplement les héberger sur un espace de stockage
compatible S3. La complexité va être de configurer correctement les
différents points de terminaison. Pour héberger nos fichiers sur notre
*bucket* S3, il va falloir suivre et appliquer quelques étapes dans
lordre :
. Configurer un bucket compatible S3 - je parlais de Scaleway, mais il y
en a - *littéralement* - des dizaines.
. Ajouter la librairie `+boto3+`, qui soccupera de "parler" avec ce
type de protocole
. Ajouter la librairie `+django-storage+`, qui va elle soccuper de
faire le câblage entre le fournisseur (*via* `+boto3+`) et Django, qui
sattend à ce quon lui donne un moteur de gestion *via* la clé
[`+DJANGO_STATICFILES_STORAGE+`](https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-STATICFILES_STORAGE).
La première étape consiste à se rendre dans [la console
Scaleway](https://console.scaleway.com/project/credentials), pour gérer
ses identifiants et créer un jeton.
image:images/deployment/scaleway-api-key.png[image]
Selon la documentation de
https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings[django-storages],
de
https://boto3.amazonaws.com/v1/documentation/api/latest/index.html[boto3]
et de
https://www.scaleway.com/en/docs/tutorials/deploy-saas-application/[Scaleway],
vous aurez besoin des clés suivantes au niveau du fichier
`+settings.py+` :
[source,python]
----
AWS_ACCESS_KEY_ID = os.getenv('ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME')
AWS_DEFAULT_ACL = 'public-read'
AWS_LOCATION = 'static'
AWS_S3_SIGNATURE_VERSION = 's3v4'
AWS_S3_HOST = 's3.%s.scw.cloud' % (AWS_S3_REGION_NAME,)
AWS_S3_ENDPOINT_URL = 'https://%s' % (AWS_S3_HOST, )
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3ManifestStaticStorage'
STATIC_URL = '%s/%s/' % (AWS_S3_ENDPOINT_URL, AWS_LOCATION)
# General optimization for faster delivery
AWS_IS_GZIPPED = True
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}
----
Cette partie pourrait éventuellement se trouver dans un fichier
`+heroku.py+`, que vous pourriez placer à côté de votre configuration
applicative. Le paramètre `+settings=+` pourrait alors pointer vers ce
nouveau fichier, pour ne pas polluer lenvironnement par défaut.
Configurez-les dans la console dadministration dHeroku:
image:images/deployment/heroku-vars-reveal.png[image]
Lors de la publication, vous devriez à présent avoir la sortie suivante,
qui sera confirmée par le *bucket*:
....
remote: -----> $ python manage.py collectstatic --noinput
remote: 128 static files copied, 156 post-processed
....
image:images/deployment/gwift-cloud-s3.png[image]
Sources complémentaires:
* [How to store Django static and media files on S3 in
production](https://coderbook.com/@marcus/how-to-store-django-static-and-media-files-on-s3-in-production/)
* [Using Django and
Boto3](https://www.simplecto.com/using-django-and-boto3-with-scaleway-object-storage/)

View File

@ -281,32 +281,6 @@ https://www.phpmyadmin.net/[PHPMyAdmin]
* Pour *SQLite*, il existe https://sqlitebrowser.org/[SQLiteBrowser]
PHPMyAdmin ou PgAdmin.
[NOTE]
====
Parfois, SQLite peut être une bonne option cite:[consider_sqlite] :
_Write througput is the area where SQLite struggles the most, but theres
not a ton of compelling data online about how it fares, so I got some of
my own: I spun up a Equinix m3.large.x86 instance, and ran a slightly
modified1 version of the SQLite kvtest2 program on it. Writing 512 byte
blobs as separate transactions, in WAL mode with synchronous=normal3,
temp_store=memory, and mmap enabled, I got 13.78latexmath:[$\mu$]s per
write, or ~72,568 writes per second. Going a bit larger, at 32kb writes,
I got 303.74latexmath:[$\mu$]s per write, or ~3,292 writes per second.
Thats not astronomical, but its certainly way more than most websites
being used by humans need. If you had 10 million daily active users,
each one could get more than 600 writes per day with that.
Looking at read throughput, SQLite can go pretty far: with the same test
above, I got a read throughput of ~496,770 reads/sec
(2.013latexmath:[$\mu$]s/read) for the 512 byte blob. Other people also
report similar results
— Expensify reports that you can get 4M QPS if youre willing to make
some slightly more involved changes and use a beefier server. Four
million QPS is enough that every internet user in the world could make
~70 queries per day, with a little headroom left over. Most websites
dont need that kind of throughput._
====
=== Bruno
https://www.usebruno.com/[Bruno] est un outil de tests d'API qui gère plusieurs protocoles (REST, SOAP, GraphQL et gRPC), plusieurs environnements et propose un système d'organisation des requêtes.

View File

@ -114,6 +114,6 @@ include::book/deployment/logs.adoc[]
include::book/deployment/native.adoc[]
include::book/deployment/docker.adoc[]
include::book/deployment/paas.adoc[]
include::book/deployment/cloud-native.adoc[]

View File

@ -65,6 +65,7 @@ include::book/django-principles/services.adoc[]
== Fly.io
include::book/annexes/poetry.adoc[]
include::book/annexes/consider-sqlite.adoc[]
= Ressources

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB