TP9 : High Throughput on Woodytoys - GuillaumeDelferiere/Admin-R-seaux_2_M1-4 GitHub Wiki

1. Configuration de l'environnement de travail

1.1 Installation et récupération du projet

# Création du répertoire de travail
mkdir -p ~/high-throughput && cd ~/high-throughput

# Clonage du repository GitHub
git clone https://github.com/xavier-dubruille/woodytoys .

1.2 Validation du fonctionnement local

# Navigation vers le dossier services
cd services

# Lancement de l'application via Docker Compose
docker compose up -d

# Validation de l'accès aux endpoints
curl http://localhost
curl http://localhost/api/misc/time

# Arrêt de l'environnement local
docker compose down

2. Préparation pour l'orchestration Docker Swarm

2.1 Script de construction et publication des images

cd ~/high-throughput

# Création du script de build automatisé
cat > publish_images.sh << 'EOF'
#!/bin/bash

set -e

registry_user="osirus705"
tag_version="1.0"

docker build -t $registry_user/woody_api:$tag_version services/api
docker build -t $registry_user/woody_rp:$tag_version services/reverse-proxy
docker build -t $registry_user/woody_database:$tag_version services/database
docker build -t $registry_user/woody_front:$tag_version services/front

docker tag $registry_user/woody_api:$tag_version $registry_user/woody_api:latest
docker tag $registry_user/woody_rp:$tag_version $registry_user/woody_rp:latest
docker tag $registry_user/woody_database:$tag_version $registry_user/woody_database:latest
docker tag $registry_user/woody_front:$tag_version $registry_user/woody_front:latest

docker login

docker push $registry_user/woody_api:$tag_version
docker push $registry_user/woody_api:latest
docker push $registry_user/woody_rp:$tag_version
docker push $registry_user/woody_rp:latest
docker push $registry_user/woody_database:$tag_version
docker push $registry_user/woody_database:latest
docker push $registry_user/woody_front:$tag_version
docker push $registry_user/woody_front:latest
EOF

chmod +x build_push.sh

# Exécution du script de publication
./build_push.sh

2.2 Fichier de configuration Docker Stack

cat > stack.yml << 'EOF'
version: "3.9"

services:
  database:
    image: osirus705/woody_database:latest
    environment:
      - MYSQL_DATABASE=woody
      - MYSQL_ROOT_PASSWORD=pass
    volumes:
      - mysql-data:/var/lib/mysql
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      restart_policy:
        condition: on-failure

  backend-api:
    image: osirus705/woody_api:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/api/ping"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.role == worker

  frontend:
    image: osirus705/woody_front:latest
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure

  proxy:
    image: osirus705/woody_rp:latest
    ports:
      - "80:8080"
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    depends_on:
      - frontend
      - backend-api

networks:
  default:
    driver: overlay

volumes:
  mysql-data:
EOF

3. Déploiement et mesures de performance initiales

3.1 Lancement de la stack sur Swarm

# Déploiement sur le nœud manager
cd ~/high-throughput
docker stack deploy -c stack.yml woodytoys-app

# Vérification du statut des services
docker stack services woodytoys-app
docker stack ps woodytoys-app

# Attente du démarrage complet
sleep 60

3.2 Tests de performance de base

# Mesure des temps de réponse standard
time wget -O - http://swarm.m1-4.ephec-ti.be > /dev/null
time curl http://swarm.m1-4.ephec-ti.be/api/misc/heavy?name=benchmark
time curl http://swarm.m1-4.ephec-ti.be/api/products/last

# Test de téléchargement récursif
time wget -r -np -nH --cut-dirs=1 http://swarm.m1-4.ephec-ti.be

4. Optimisation par scaling horizontal

4.1 Augmentation des instances d'API

# Scaling du service API
docker service scale woodytoys-app_backend-api=5

# Contrôle du scaling
docker service ls

4.2 Évaluation post-scaling

# Nouvelles mesures de performance
time wget -O - http://swarm.m1-4.ephec-ti.be > /dev/null
time curl http://swarm.m1-4.ephec-ti.be/api/misc/heavy?name=benchmark
time curl http://swarm.m1-4.ephec-ti.be/api/products/last

# Test de charge parallèle
for i in {1..5}; do
  curl -s http://swarm.m1-4.ephec-ti.be/api/misc/heavy?name=test$i &
done
wait

5. Implémentation du cache avec Redis

5.1 Développement du module de cache

# Création du dossier pour l'API avec cache
mkdir -p ~/high-throughput/api-with-cache && cd ~/high-throughput/api-with-cache

# Copie des sources originales
cp -r ~/high-throughput/services/api/* .

# Module de gestion du cache Redis
cat > cache_manager.py << 'EOF'
import redis
import json
from functools import wraps

redis_connection = redis.Redis(host='redis', port=6379, db=0)

def cached_response(expiry_time=60):
    """
    Décorateur pour la mise en cache des réponses
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Construction de la clé de cache unique
            cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
            
            # Tentative de récupération depuis le cache
            cached_data = redis_connection.get(cache_key)
            if cached_data:
                print(f"Cache hit pour {cache_key}")
                return cached_data.decode('utf-8')
            
            # Exécution de la fonction et mise en cache
            print(f"Cache miss pour {cache_key}")
            result = func(*args, **kwargs)
            redis_connection.setex(cache_key, expiry_time, result)
            return result
        return wrapper
    return decorator
EOF

# API modifiée avec cache Redis
cat > main.py << 'EOF'
import uuid
from datetime import datetime

from flask import Flask, request
from flask_cors import CORS

import woody
from cache_manager import cached_response
import redis

app = Flask('woody_api')
cors = CORS(app)

redis_connection = redis.Redis(host='redis', port=6379, db=0)

@app.get('/api/ping')
def health_check():
    return 'healthy'

@app.route('/api/misc/time', methods=['GET'])
def current_time():
    return f'timestamp: {datetime.now()}'

@app.route('/api/misc/heavy', methods=['GET'])
@cached_response(expiry_time=30)
def heavy_computation():
    name = request.args.get('name')
    result = woody.make_some_heavy_computation(name)
    return f'{datetime.now()}: {result}'

@app.route('/api/products', methods=['GET'])
def create_product():
    product_name = request.args.get('product')
    woody.add_product(str(product_name))
    redis_connection.delete('get_latest_product:():{}')
    return str(product_name) or "none"

@app.route('/api/products/<int:product_id>', methods=['GET'])
def fetch_product(product_id):
    return "fonctionnalité en développement"

@app.route('/api/products/last', methods=['GET'])
@cached_response(expiry_time=15)
def get_latest_product():
    latest_product = woody.get_last_product()
    return f'database: {datetime.now()} - {latest_product}'

@app.route('/api/orders/do', methods=['GET'])
def process_order():
    order_product = request.args.get('order')
    order_uuid = str(uuid.uuid4())
    handle_order(order_uuid, order_product)
    return f"Commande {order_uuid} créée pour le produit : {order_product}"

@app.route('/api/orders/', methods=['GET'])
def check_order():
    order_uuid = request.args.get('order_id')
    order_status = woody.get_order(order_uuid)
    return f'commande "{order_uuid}": {order_status}'

def handle_order(order_id, product):
    validation_status = woody.make_heavy_validation(product)
    woody.save_order(order_id, validation_status, product)

if __name__ == "__main__":
    woody.launch_server(app, host='0.0.0.0', port=5000)
EOF

5.2 Construction et déploiement avec Redis

# Build de l'image avec cache
docker build -t osirus705/woody_api:with-redis .
docker push osirus705/woody_api:with-redis

# Configuration stack avec Redis
cd ~/high-throughput
cat > docker-stack-redis.yml << 'EOF'
version: "3.9"

services:
  database:
    image: osirus705/woody_database:latest
    environment:
      - MYSQL_DATABASE=woody
      - MYSQL_ROOT_PASSWORD=pass
    volumes:
      - mysql-data:/var/lib/mysql
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      restart_policy:
        condition: on-failure

  cache-server:
    image: redis:alpine
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure

  backend-api:
    image: osirus705/woody_api:with-redis
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/api/ping"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    deploy:
      replicas: 5
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.role == worker

  frontend:
    image: osirus705/woody_front:latest
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure

  proxy:
    image: osirus705/woody_rp:latest
    ports:
      - "80:8080"
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    depends_on:
      - frontend
      - backend-api

networks:
  default:
    driver: overlay

volumes:
  mysql-data:
EOF

# Récupération de l'image Redis
docker pull redis:alpine

# Déploiement avec cache Redis
docker stack deploy -c docker-stack-redis.yml woodytoys-app

# Attente du démarrage
sleep 60

5.3 Tests de performance avec cache

# Test initial (cache miss)
time curl http://swarm.m1-4.ephec-ti.be/api/misc/heavy?name=benchmark

# Test suivant (cache hit)
time curl http://swarm.m1-4.ephec-ti.be/api/misc/heavy?name=benchmark

# Test API produit (cache miss)
time curl http://swarm.m1-4.ephec-ti.be/api/products/last

# Test API produit (cache hit)
time curl http://swarm.m1-4.ephec-ti.be/api/products/last

6. Intégration CDN

6.1 Configuration du service CDN Gcore

  1. Inscription sur https://gcore.com/
  2. Navigation vers "CDN" > "Resources" dans le tableau de bord
  3. Configuration d'une nouvelle ressource :

6.2 Configuration DNS pour le CDN

# Configuration sur le serveur DNS
cd ~/dns-public

# Ajout du CNAME dans la zone DNS
echo "cdn   IN   CNAME   XXXXX.cdn.gcore.com." >> zone/m1-4.zone

# Mise à jour du serial dans le SOA

# Redémarrage du service DNS
docker restart dns

# Test de résolution DNS
dig @localhost cdn.m1-4.ephec-ti.be

6.3 Adaptation du frontend pour le CDN

mkdir -p ~/high-throughput/frontend-cdn && cd ~/high-throughput/frontend-cdn

# Copie des fichiers frontend
cp -r ~/high-throughput/services/front/* .

# Modification de index.html pour utiliser le CDN
# Remplacement de /5mo.jpg par https://cdn.m1-4.ephec-ti.be/5mo.jpg dans index.html

# Construction et publication de l'image
docker build -t osirus705/woody_front:cdn-enabled .
docker push osirus705/woody_front:cdn-enabled

6.4 Déploiement de la version optimisée CDN

cd ~/tp-high-throughput
cat > docker-stack-complete.yml << 'EOF'
version: "3.9"

services:
  database:
    image: osirus705/woody_database:latest
    environment:
      - MYSQL_DATABASE=woody
      - MYSQL_ROOT_PASSWORD=pass
    volumes:
      - mysql-data:/var/lib/mysql
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      restart_policy:
        condition: on-failure

  cache-server:
    image: redis:alpine
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure

  backend-api:
    image: osirus705/woody_api:with-redis
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/api/ping"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    deploy:
      replicas: 5
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.role == worker

  frontend:
    image: osirus705/woody_front:cdn-enabled
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure

  proxy:
    image: osirus705/woody_rp:latest
    ports:
      - "80:8080"
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    depends_on:
      - frontend
      - backend-api

networks:
  default:
    driver: overlay

volumes:
  mysql-data:
EOF

# Déploiement de la configuration complète
docker stack deploy -c docker-stack-complete.yml woodytoys-app

# Pause pour le démarrage
sleep 60

6.5 Tests de performance avec CDN

# Mesure du temps de chargement de page
time wget -O - http://swarm.m1-4.ephec-ti.be > /dev/null

# Test de téléchargement récursif optimisé
time wget -r -np -nH --cut-dirs=1 http://swarm.m1-4.ephec-ti.be

7. Analyse des résultats

7.1 Comparaison des performances

Version Chargement récursif API calcul intensif API base de données
Baseline 16.065 s 5.029 s 15.198 s
Avec replicas 16.093 s 5.040 s 15.169 s
Avec Redis 16.102 s 5.058 s (miss) / 0.021 s (hit) 15.214 s (miss) / 0.032 s (hit)
Avec CDN 0.030 s identique identique

7.2 Analyse des améliorations

  1. Scaling horizontal :

    • Impact sur l'API : Amélioration limitée due à la nature single-thread de Flask
    • Impact sur les requêtes DB : Pas d'amélioration notable car la DB reste un goulot d'étranglement
  2. Mise en cache Redis :

    • Impact majeur sur les appels répétés
    • Gain de 99.6% sur l'API calcul intensif et 99.8% sur l'API DB lors d'un hit cache
  3. CDN :

    • Amélioration drastique pour les ressources statiques
    • Réduction de 99.8% du temps de chargement global

8. Stratégies d'amélioration pour la base de données

Pour optimiser davantage les performances de la base de données, plusieurs approches sont envisageables :

8.1 Architecture Master-Slave

Cette approche sépare les opérations de lecture et d'écriture :

  • Un serveur maître gère les opérations d'écriture
  • Des serveurs esclaves répliqués gèrent les opérations de lecture

Bénéfices :

  • Amélioration de la scalabilité en lecture
  • Distribution efficace de la charge
  • Amélioration de la disponibilité

8.2 Pattern CQRS (Command Query Responsibility Segregation)

Le CQRS sépare :

  • Le modèle de commandes (écritures) : optimisé pour les transactions ACID
  • Le modèle de requêtes (lectures) : optimisé pour les performances, potentiellement dénormalisé

Bénéfices :

  • Optimisation spécialisée pour chaque type d'opération
  • Amélioration des performances de lecture
  • Évolutivité indépendante des composants

8.3 Partitionnement horizontal (Sharding)

Le sharding divise les données sur plusieurs serveurs selon une stratégie de partitionnement.

Bénéfices :

  • Scalabilité horizontale quasi-infinie
  • Distribution équilibrée de la charge
  • Performance améliorée pour les requêtes ciblées