Working on deployments processes
This commit is contained in:
parent
8fa688c380
commit
910926b66e
|
@ -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 there’s
|
||||
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.
|
||||
That’s not astronomical, but it’s 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 you’re 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
|
||||
don’t need that kind of throughput._
|
|
@ -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. Docker’s 2014 revenue was less than $10 million cite:[roads_and_bridges(48)].
|
||||
====
|
||||
|
||||
=== Docker-compose
|
||||
|
||||
D’abord, après avoir installer docker-compose et les dépendances sous debian, tu dois t’ajouter dans le groupe docker, sinon il faut être root pour utiliser docker.
|
||||
|
||||
Bon après c’est facile, un petit virtualenv pour cookiecutter, suivi d’une installation du template django. Et puis j’ai 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
|
||||
|
||||
L’image de base python passe de 179 à 740 MB.
|
||||
Et là j’en ai pour presque 1,5 GB d’un coup.
|
||||
|
||||
Mais par contre, j’ai 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 qu’un accès en lecture au dépôt source.
|
||||
|
||||
=== Portainer
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
== Docker & Portainer
|
|
@ -1 +1,576 @@
|
|||
== Native
|
||||
== Debian/Ubuntu
|
||||
|
||||
image::deployment/debian-ubuntu.png[align="center"]
|
||||
|
||||
Le déploiement sur Debian est présenté dans le cadre d’un 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 l’application,
|
||||
* Il n’y 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 l’investissement 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 l’artisanat (bien qu’Ansible "professionalise" sérieusement le tout)
|
||||
|===
|
||||
|
||||
Le choix de ces deux systèmes d’exploitation s’explique 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 jusqu’en 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 s’il le juge nécessaire (_Supervisord_),
|
||||
. 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, pour exécution (_Nginx, Apache, ...).
|
||||
|
||||
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, 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 l’interpré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 d’exé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 l’ordre, 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. L’utilisateur 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, l’ensemble 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 l’installer *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 l’interpréteur initialement supporté par la distribution, et cela pourrait avoir des effets de bord non souhaités.
|
||||
|
||||
==== Base de données
|
||||
|
||||
On l’a 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 n’est 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 d’un 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 l’utilisateur système, il n’est 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 l’utilisateur | |
|
||||
|
||||
|Création de la base de données | |
|
||||
|
||||
|Dump | |
|
||||
|===
|
||||
|
||||
====
|
||||
|
||||
|
||||
|
||||
=== Préparation de l’environment 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 l’application
|
||||
|
||||
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 qu’il 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 l’application est en train de tourner, à l’aide 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 l’on
|
||||
verra plus tard. L’objectif est que les fichiers ne demandant aucune
|
||||
intelligence soit directement servis par Nginx. Cela évite d’avoir 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
|
||||
|
||||
===== Let’s 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+`.
|
||||
|
||||
C’est l’utilisateur gwift qui s’en 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 d’une première fois, mais est relativement rapide par la suite.
|
||||
Comptez une heure ou deux lorsque vous aurez pris l’habitude.
|
||||
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.
|
||||
|
|
|
@ -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 l’hô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 d’autres 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 l’exemple de déploiement ci-dessous, il est tout à fait possible de s’en 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 d’investir 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 qu’aucun mécanisme de migration n’est prévu.
|
||||
Ceci signifie qu’aprè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 n’existe 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 d’envoyer les sources de votre application vers ce dépôt pour qu’Heroku 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 d’inté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é d’utiliser 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 d’espace de
|
||||
stockage. Il est possible d’y 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 l’hé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 d’environnement pour les réutiliser
|
||||
ci-dessous.
|
||||
|
||||
Vous aurez peut-être besoin d’un 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 d’application
|
||||
|
||||
Pour qu’Heroku comprenne le type d’application à 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* l’argument `+-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
|
||||
l’https://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 d’espace pour jouer un peu.
|
||||
|
||||
image:images/deployment/scaleway-object-storage-bucket.png[image]
|
||||
|
||||
L’idée est qu’au 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
|
||||
l’ordre :
|
||||
|
||||
. Configurer un bucket compatible S3 - je parlais de Scaleway, mais il y
|
||||
en a - *littéralement* - des dizaines.
|
||||
. Ajouter la librairie `+boto3+`, qui s’occupera de "parler" avec ce
|
||||
type de protocole
|
||||
. Ajouter la librairie `+django-storage+`, qui va elle s’occuper de
|
||||
faire le câblage entre le fournisseur (*via* `+boto3+`) et Django, qui
|
||||
s’attend à ce qu’on 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 l’environnement par défaut.
|
||||
|
||||
Configurez-les dans la console d’administration d’Heroku:
|
||||
|
||||
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/)
|
|
@ -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 there’s
|
||||
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.
|
||||
That’s not astronomical, but it’s 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 you’re 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
|
||||
don’t 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.
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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 |
Loading…
Reference in New Issue