Master 2, Bases de données avancées, année 2024
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 certaines variables à certaines 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 variables sont locales au processus bash en cours et d’autres sont des variables d’environnement qui contiennent des informations que les programmes utilisent pour personnaliser leur interaction 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 à se rappeler des variables comme les adresse IP, les mot de passes, etc, il est possible de mettre ces informations dans un fichier texte:
vm1=192.100.00.11 # une adresse 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ésentes dans les commandes fournies
- Soit fixer les variables dans le shell ou dans l’environnement une fois et faire des copier/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épôts 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 connexions 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 messages 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 faites jamais ça dans des vrais cas d’usage, un trafic réseau non chiffré implique que les mots de passe ne sont plus sécurisés. Il vaut mieux utiliser le client fourni.
À l’aide de telnet, connectez vous à l’adresse IP de
redis
au port par défaut (6379).
telnet $redis_ip 6379
Vous pouvez alors écrire des commandes simples. Vérifiez que ça marche correctement.
Pour quitter, utilisez la commande redis QUIT
.
n’utilisez plus telnet, c’est juste pour montrer la simplicité 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 dur 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'adresse 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 clefs et des valeurs
Dans redis, les clefs et les valeurs sont des suites de bytes et non typés.
Les clients souvent encodent ça sous forme d’un type textuel ce qui peut entraîner des clash indésirables.
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 a
transformé l’entier 42
en une chaîne de caractères.
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[
AU CHOIX:
- Continuez le TP sur les middleware (Redis et API)
- Basculez sur un TP sur l’élasticité (Python et table de hashage distribuée)
Les intergiciels (ou middleware)
La majorité du temps la base de données n’est pas exposée 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 intergiciel 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 langage 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éveloppeurs.
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 goulot 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 installezlighttpd
.
Dans la suite on note $middleware_ip
l’adresse ip de
cette VM.
- Vérifiez 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érifiez à 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ée au web qui respecte le protocole HTTP et offre des garanties supplémentaires.
Les méthodes HTTP s’appuient sur trois composantes:
- La requête:
GET
,POST
,PUT
,PATCH
etDELETE
. Cela désigne l’action à 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 prix pour
les projets importants, tout comme CGI
qui souffre de vrais
problèmes de performance. 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 simples et de les rediriger.
Activez le module mod_rewrite
et ajoutez 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écutez la commande
curl $middleware_ip/une/url/foireuse?avec=donnée
. En
analysant le retour, indiquez quelle variable d’environnement stock
l’URL initiale avec réécriture par lighttp
.
N’oubliez de recharger lighttpd après les modification des fichiers de configurations
sudo systemctl reload lighttpd
CGI avec Python
Pour ceux d’entre vous qui le souhaitent, il est possible d’utiliser Java.
Enlever les règles de réécriture 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éer 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 se 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 pour 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 informations de connexion
- 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érents:
- La base de donnée 0 ne contient rien d’autre que ce qu’on a 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 versions 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 noms 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 informations 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 envoie 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/
.
Attentions, vous devez installer redis sur l’ensemble du système du
midleware et pas uniquement en local. Utilisez pour ça apt
et pas pip
(qui install le packet uniquement pour
l’utilisateur local et donc pas l’utilisateur www-data qui execute les
scripts via CGI).
sudo apt install python3-redis
- 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.
- Encrypte le mot de passe à l’aide du module
hashlib
et calcule 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 timestamps de connexion, 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 identiques 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
Faites 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ée (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 connexion.
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 connexion 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 10 minutes la session.
- 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 standard 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érée par le même fichier (éventuellement décomposé en sous fichier importé pour plus de clarté). La liste des fichiers de chaque utilisateur doit être maintenue à 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érente. 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
retours à la ligne…
Compiled the: sam. 23 nov. 2024 08:37:24 CET