Master 2, Bases de données avancées, année 2021
Objectif
- Se familiariser avec le key-value store Redis
- Avoir une idée des performances de Redis sur la plateforme OpenStack
- Mettre en place un cluster Redis et évaluer l’impact sur les performances
Rappels sur
bash
et l’environnement
Dans bash (le terminal, pour faire simple), il est possible de fixer certaine variable a certaine valeurs:
a=42
Donne à la variable a
la valeur 42
.
Il est possible d’utiliser cette valeur dans toute
commande ce qui est bien pratique pour éviter de se souvenir de
certaines informations. Pour utiliser une variable, on utilise la
syntaxe $nom_variable
. Donc par exemple:
echo La variable a vaut: $a
retourne
La variable a vaut: 42
Certaines variable sont local au processus bash en cours et d’autres sont des variables d’environnements qui contiennent des informations que les programmes utilisent pour personnalisé leur intéraction avec l’utilisateur.
Pour afficher les variables d’environnement, il suffit d’utiliser
env
Pour exporter la variable locale a
en une variable
d’environnement il suffit d’utiliser
export a
Cet export n’est valable que pour le processus (et les sous-processus) qui ont réalisé l’export.
Pour ne pas avoir a se rappeler des variables comme les adresse IP, les mot de passes, ect, il est possible de mettre ces information dans un fichier texte:
vm1=192.100.00.11 # une addresse ip
pass1=1234 # un très mauvais mot de passe
Il est possible de charger ces variables à l’aide de la commande
source chemin/fichier
où chemin/fichier
est le
chemin vers votre fichier.
Dans les TP, j’utilise ces notations pour les commandes. Vous pouvez:
- Soit substituer à la main les variables (avec $) présente dans les commandes fournies
- Soit fixé les variables dans le shell ou dans l’environnement une fois et faire des copié/coller
Installation Redis
- Créez une nouvelle VM dédiée à Redis que vous nommez
redis
.
Dans la suite, on nommera $redis_ip
l’adresse IP de
cette VM.
- Installez
Redis
et à l’aide de la documentation configurez les accès distants pour autoriser les requêtes
Redis est packagé dans les dépots et peut donc être installé très simplement (et rapidement):
Oubliez pas de rafraichir les dépôts avec
sudo apt update
$ sudo apt install redis
Vérifiez que redis
est installé à l’aide de la
commande
redis-cli --version
- Modifier le fichier de config
/etc/redis/redis.conf
pour:
- autoriser les connections distantes
- ajouter un mot de passe (
$mot_de_passe
) que vous m’indiquez dans le rendu.
Il est possible d’avoir une gestion plus fine des accès, mais ce n’est pas la peine de rentrer dans ce genre de détail ici (pour les curieux)
- À l’aide de
redis-cli
faite les commandes suivante:
SET big_answer "42"
GET big_answer
(ça doit retourner 42)SELECT 1
GET big_answer
(ça doit retourner une erreur)
À quoi sert la commande SELECT
?
Utilisation simple de Redis
Redis fournit une interface simple. On peut s’y connecter en TCP/IP et envoyer des message et avoir la réponse. Il est donc possible de bricoler un client très rapidement, quelque soit l’environnement.
Par exemple, en ligne de commande il est possible d’utiliser
telnet
qui offre une connexion (non chiffrée).
Ne fait jamais ça dans des vrais cas d’usage, un trafic réseau non chiffré implique que les mots de passes ne sont plus sécurisé. Il vaut mieux utiliser le client fourni.
À l’aide de telnet, connecté vous à l’addresse IP de
redis
au port par défaut (6379).
telnet $redis_ip 6379
Vous pouvez alors écrire des commandes simple. Vérifier que ça marche correctement.
Pour quitter, utiliser la commande redis QUIT
.
n’utilisez plus telnet, c’est juste pour montrer la simpliciter du protocole
Utilisation applicative de Redis
La plupart du temps, un langage de programmation fournira un
connecteur à Redis
. On a vu, il est pas trop dure d’en
bricoler un si ce n’est pas le cas, mais ça reste un bricolage à ne pas
utiliser en production. En Python, on peut utiliser le module
redis
.
Pour l’installer, pip3 install --user redis
devrait
marcher quelque soit la machine, même sans les droits administrateurs.
Attention, pour installer Redis
sur tout le système
utilisez la commande sudo pip3 install redis
. Cela peut
être utile pour avoir Redis
sur une VM.
import redis
= redis.Redis(host="100.42.42.42", port=6379, password="chocolat", db=1) # changer l'addresse ip et le mot de passe
r "test_python"] = 42 r[
Vous pouvez vérifier que la clef test_python
est bien
arrivé dans la base de donnée.
À propos du type des clef et des valeurs
Dans redis, les clefs et les valeurs sont des suites de bytes et non typés.
Les clients souvent encode ça sous forme d’un type textuel ce qui peut entraîner des clash indésirable.
Par exemple, dans le client Python
"42"] = "La réponse à la grande question"
r[42] = 0 r[
Alors r["42"]
retournera 0
, car le client à
transformer l’entier 42
en une chaine de caractère.
Il est tout à fait possible d’utiliser des fichiers binaires comme valeurs (et même comme clef, même si c’est déconseillé). Ainsi on peut stocker des images facilement:
with open("mon_logo.png", "rb") as f:
"mon_logo"] = f.read() r[
Les intergiciels (ou middleware)
La majorité du temps la base de données n’est pas exposé directement sur le réseau mais accessible via un intergiciel.
Le rôle de ce dernier est de faire le lien entre les clients et les bases de données, de distribuer la charge quand ces dernières sont distribuées. Un integiciel peut être dépendant d’un SGBD ou être fait à la main en fonction des besoins architecturaux. La plupart des SGBD proposent des connecteurs dans toutes les langages de programmation donc il est aisé d’en construire dans votre langages de prédilection.
Par défaut, Python fait bien l’affaire, mais le plus commun est en
fait PHP
. Il s’interconnecte facilement avec les serveurs
web standards (comme Apache) et possède un gros vivier de développeur.
Ici nous allons allez au plus simple:
On va construire un petit mécanisme CGI
en bash, mais c’est simple d’adapter ça au langage de programmation de
votre choix.
CGI
est très simple à mettre en place, mais très peu
efficace car nécessite un processus par requête HTTP
.
fastCGI
permet de contourner ce goulôt d’étranglement en
ajoutant un serveur intermédiaire qui s’occupe d’exécuter le code dans
des processus qui tourne en permanence. En Python, il est aussi possible
d’utiliser WSGI
.
- Déployez une nouvelle
VM
nomméemiddleware
et installerlighttpd
.
Dans la suite on note $middleware_ip
l’addresse ip de
cette VM.
- Vérifier que
lighttpd
fonctionne en créant une page html contenantHello World!
dans/var/www/html/
et en exécutant localement:
curl $middleware_ip
Vous devriez avoir Hello World!
comme réponse.
- Activez le mode
mod_cgi
à l’aide de la commandelighty-enable-mod
et ajoutez les lignes suivantes au fichier de configuration du module (qui est/etc/lighttpd/conf-available/10-cgi.conf
.
cgi.assign = (
".cgi" => "/usr/bin/bash",
)
Ainsi vous indiquez à lighttpd
quel interpréteur
utiliser pour exécuter les fichiers .cgi
. Rechargez la
configuration à l’aide de
service lighttpd force-reload
.
- Créez un script
Bash
nomméhello.cgi
dans le dossier/var/www/html
contenant
echo This is CGI!
Vérifier à l’aide de curl $middleware_ip/hello.cgi
que
le fichier est bien exécuté par lighttpd
. Vous devriez
avoir uniquement This is CGI
comme réponse et pas
echo
.
- Créez un script
env.cgi
dans le dossier/var/www/html
contenant la commandeprintenv
. Que retournecurl $middleware_ip/env.cgi
?
Voici des erreurs courantes:
Le serveur web ne démarre pas car le fichier de configuration a une erreur. Pour vérifier que le serveur web fonctionne:
systemctl show lighttpd
Le fichier CGI ne retourne rien, cela peut déclencher une erreur 500.
Les API REST
Une API Rest est une API dédié au web qui respecte le protocole HTTP et offre des garanties supplémentaires.
Les méthodes HTTP s’appuie sur trois composantes:
- La requête:
GET
,POST
,PUT
,PATCH
etDELETE
. Cela désigne l’action a effectuer sur le serveur. - La ressource (
uri
): typiquement le lien
Dans la requête, il est possible d’ajouter des données qui sont
transmise via stdin au script cgi
.
Il est possible de communiquer avec un serveur, simplement en ligne
de commande avec la commande curl
. À l’aide de l’option
-X
(ou sa version longue --request
on peut
ainsi faire une requête POST
, PUT
,
PATCH
et DELETE
).
- Ajoutez
cat <& 0
àenv.cgi
et exécutezcurl --data "Holla Mundo!" $middleware_ip/env.cgi?halloWelt
. En conclure comment les donnéesGET
etPOST
sont transmises à l’application en CGI.
Les intergiciels en Bash
sont a éviter à tout pris pour
les projets important, tout comme CGI
qui souffre de vrai
problème de performances. En pratique, il faudrait utiliser
FastCGI
ou une autre technologie récente et un langage de
programmation adapté. Typiquement Python
propose de
nombreux framework
pour déployer des intergiciels (voir par
exemple flask)
Réécriture d’URL
Il est très souvent utile de permettre à un utilisateur d’utiliser des url simple et de les rediriger.
Activer le module mod_rewrite
et ajouter au fichier de
configuration /etc/lighttpd/conf-available/10-rewrite.conf
la ligne url.rewrite-once = ( "^/(.*)" => "/env.cgi" )
qui redirige toutes les requêtes vers le fichier cgi
.
Exécuter la commande
curl $middleware_ip/une/url/foireuse?avec=donnée
. En
analysant le retour, indiquer quelle variable d’environnement stock
l’URL initiale avec réécriture par lighttp
.
CGI avec Python
Pour ceux d’entres vous qui le souhaitent, il est possible d’utiliser Java.
Enlever les règles de réécritures précédentes, nous allons avoir besoin d’en construire de nouvelles.
Modifier
/etc/lighttpd/conf-available/10-cgi.conf
pour n’autoriser que les fichiers Python
cgi.assign = (
".py" => "/usr/bin/python3",
)
Modifier
rewrite
pour rediriger l’URLenv
vers le fichier/env.py
Créez un fichier
env.py
dans/var/www/html/
avec le contenu suivant:
import os, sys
print("\n".join(f"{key}:{value}" for key,value in os.environ.items()))
- Que ce passe t’il quand on consulte la page
curl $middleware_ip/env
?
Un intergiciel et Redis pour une petit application web
Nous allons utiliser un intergiciel et Redis une petite application permettant:
- de créer un utilisateur avec un mot de passe
- de lui permettre de se connecter avec un token de session à durée limitée
- de lui donner des information de connexions
- de lui offrir un stockage de fichier simple sur le serveur
- de supprimer un utilisateur et toutes ses données
On va assigner des bases de données des rôles différentes:
- La base de donnée 0 ne contient rien d’autre que ce qu’on ajouté dans le TP et des informations statistiques (nombres d’utilisateurs, nombre de fichiers).
- La base de donnée 1 contient en clefs les
user_name
et en valeur son identifiantuuid
- La base de donnée 2 contient en clefs les
uuid
(d’utilisateur) et en valeur des informations sur l’utilisateur (user_name
associé, mots de passe, nombres de fichiers, log de connexion) - La base de donnée 3 contient en clefs les
uuid
de fichier et associe les différentes version du fichier stocké sous forme de liste. - La base de donnée 4 contient en clefs les
uuid
d’utilisateurs et retourne une association de nom de fichiers à leur uuid. - La base de donnée 5 contient en clefs des token (
uuid
) à durée limitée et en valeur l’uuid
associé à l’utilisateur.
Pour communiquer avec l’application, nous allons utiliser le protocole HTTP de manière standard.
- Pour créer un compte, on envoie via les données
POST
à$middleware_ip/create
avec le nom d’utilisateur et le mot de passe. - Pour se connecter, on envoie via les données
POST
à$middleware_ip/connect
avec le nom d’utilisateur et le mot de passe et on obtient un token en retour. - Pour avoir des information sur le compte, une requête
GET
à$middleware_ip
avec le token de connexion en cookie. - Pour ajouter un fichier, on envoie le fichier via les données
POST
à `$middleware_ip/files/nom_de_fichier avec le token en cookie - Pour récupérer la liste des fichiers, on en voir une requête
GET
à$middleware_ip/files/
le token associé. - Pour télécharger un fichier, on utilise l’URL
$middleware_ip/files/nom_de_ficher
avec le token en cookie.
Nous allons créer un intergiciel qui orchestre tout ça. Vous pouvez
utiliser le langage de programmation de votre choix, mais
Python
est recommandé.
La création de compte
Créez un fichier
redis_connect.py
qui propose une fonctionconnection_redis
prend en argument un entier qui retourne un connecteurredis
pour la base de donnée passée en argument. Pour ne pas l’exposer à l’extérieur, rediriger l’urlredis_connect.py
vers/
.Créez un fichier
create.py
(oucreate.cgi
ou autre si autre langage) et redirigez l’urlcreate
vers lui. Faites en sortes que le scriptecreate.py
:
- Lise sur
stdin
le nom d’utilisateur et le mot de passe. Si c’est mal formaté, retourne une erreur HTTP. - Vérifie si le nom d’utilisateur est dans la base de donnée 1, si Oui, retourne une erreur HTTP.
- Encripte le mot de passe à l’aide du module
hashlib
et calcul un uuid pour l’utilisateur à l’aide du moduleuuid
- Dans la base de donnée 1, associez le nom d’utilisateur à l’uid calculé
- Dans la base de donnée 2, associez un hashmap contenant le nom d’utilisateur, le hash du mot de passe (éventuellement des timestamp de connection, et de création de compte).
Les commandes Redis
pour manipuler les
hashmap
sont hset
, hget
,
hmget
, hmset
et hgetall
. En
python, les binding sont identitiques et permettent de stocker un
dictionnaire (dont les clefs valeurs sont des types simples). Pour plus
de détails, voir la documentation de Redis.
Connexion au compte
Faite une redirection de l’url
/connect.py
vers/connect
Créez un fichier
connect.py
qui:
- lit sur
stdin
un formulaire de connexion (user=password
), - récupère l’uid qui correspond ainsi que le hash du mot de passe stocké dans la base de donnée
- vérifie que les hash des deux mots de passes coincident.
- Si le nom d’utilisateur n’existe pas, retourne une erreur “User unknown”
- Si les hash des mots de passe de correspondent pas, retourne une erreur “Wrong password”
- Dans le cas contraire, génère un uuid temporaire, associe cet uuid à l’uuid utilisateur pour une durée limité (10minutes) dans la base de donnée 5.
- retourne l’uuid temporaire sous la forme d’un cookie
sessionId
à l’aide de la commandeHTTP
Set-Cookie - met à jour dans les informations la date dernière connection.
Pour la durée limitée, on doit utiliser la commande expire
de
Redis
Avoir des informations sur le compte
Faites une redirection de l’url
/
vers/info.py
Créez un fichier
info.py
qui va:
- soit ne retourne rien s’il n’y a pas de cookie de connection temporaire
- soit retourne un message d’erreur si le cookie n’est plus valable
- soit retourne des informations associées au compte si le cookie est valide. Vous pouvez prolonger de 10minutes la sessions.
- Les cookies sont passés via la variable d’environnement
HTTP_COOKIE
que vous pouvez aisément récupérer. - Vous pouvez retourner les informations au format
json
. Pour respecter le standar vous pouvez utiliser:content-type: application/json
pour indiquer au client que vous envoyez du JSON.
API d’upload et de download de fichier
Faites une redirection de toutes les URL de la forme
/files/\*
vers /gestion_fichier.py
et créez le
fichier gestion_fichier.py
La gestion de fichier va être gérer par le même fichier (éventullement décomposé en sous fichier importé pour plus de clarté). La liste des fichiers de chaques utilisateur doit être maintenu à l’aide d’un type de donnée list. Vous pouvez lire la liste des commandes ici.
En fonction du type de requête HTTP
on va réaliser une
action différence. Les 4 requêtes à prendre en compte sont:
GET
, PUT
, PATCH
et
DELETE
. La requête GET
va par ailleurs
retourner la liste de fichiers pour les requêtes de la forme
$remote_ip/files/
(c’est-à-dire, sans nom de fichiers
supplémentaires).
Attention pour envoyer des fichiers avec curl, utiliser l’option
--data-binary
, l’option --data
supprime les
retour à la ligne…
Compiled the: mer. 04 sept. 2024 12:50:05 CEST