Cluster de Raspberry Pi – Part2

Installation Docker Manager

  • Le Raspberry Pi 3 Model A+
  • Broadcom BCM2837B0, Cortex-A53 (ARMv8) 64-bit SoC @ 1.4GHz
  • 512MB LPDDR2 SDRAM
  • Créer l’image DietPi
  • https://dietpi.com/#downloadinfo
  • Configuration avant le premier boot dans les fichiers dietpi et dietpi-wifi
  • Login SSH : root / dietpi
  • Script de configuration dietpi-config
  • OS de base 41 Mo de RAM utilisé

​​​image

image

Installation de Docker

# Add Docker's official GPG key:
apt-get update
apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/raspbian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/raspbian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update

# Install Docker
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Pour tester un container

docker run hello-world

image

Une fois Docker installé nous sommes à 80 Mo de RAM utilisé

image

  • Pour simplifier l’administration, je vais utiliser Portainer

docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce

image

  • Une fois tout le Docker Manager installé nous sommes à 111 Mo de RAM utilisé, ce qui laisse pas mal de marge.

image

  • Nous activons le Docker Swarm avec ce Rpi en tant que Manager
docker swarm init

image

  • Cela a activé automatiquement les fonctionnalitésDocker Swarm dans Portainer

image

image

Creation d’un service avec Docker Swarm

  • Test avec un container basique NGinx sur uniquement le Manager pour le moment
  • Je créé 4 réplicas du meme conainer NGinx
version: "3.9"

services:
  nginx:
    image: nginx:latest
    ports:
      - "30000:80"  # Mappe le port 80 du conteneur sur le port 30000 de l'hôte
    deploy:
      replicas: 4  # Déploie 4 répliques du service

    volumes:
      - /home/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro  # Monte le fichier de conf Nginx
      - /home/docker/nginx/data:/usr/share/nginx/html:ro       # Monte le répertoire des pages HTML
    networks:
      - nginx_network

networks:
  nginx_network:
    driver: overlay  # Utilise un réseau overlay pour Swarm
  • Avant d’éxecuter le stack il faut aller créer un fichier nginx.conf
# Global Nginx configuration
events {
    worker_connections 1024;
}

http {
    # Inclure ici toutes les configurations des serveurs
    server {
        listen 80;

        location / {
            add_header X-Container-ID $hostname;
            root /usr/share/nginx/html;  # Répertoire où les fichiers HTML sont montés
            index index.html;
        }

        location /about {
            add_header X-Container-ID $hostname;
            root /usr/share/nginx/html;
            index about.html;
        }
    }
}
  • On démarre le Stack et l’on voit bien nos 4 containers en route

image

  • Par defaut Docker fait du Load Balancer / Round-Robin entre les containers
  • Lorsque vous publiez un service en mode Swarm avec deploy.replicas​ et que vous exposez un port en mode ingress (mode par defaut) (via ports​ dans le docker-compose.yml​), Docker active automatiquement un load balancer distribué.
    • Les requêtes entrantes sur le port publié (par exemple, 30000​) sont distribuées en round-robin entre toutes les répliques disponibles.
    • Dans mon exemple, les requêtes se répartissent ainsi :
    • Requête 1 → Conteneur 1
    • Requête 2 → Conteneur 2
    • Requête 3 → Conteneur 3
    • Requête 4 → Conteneur 4
    • Requête 5 → Conteneur 1 (cycle répété)
    • Voila une page permettant d’indiquer sur qu’elle container je me trouve lorsque que j’interoge le serveur web, je vais la nommer index.html dans le /home/docker/nginx/data (chemin contenant les pages comme indiqué dans le stack)
<!DOCTYPE html>
<html>
<head>
    <title>Container Identification</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin-top: 50px;
        }
        h1 {
            color: #4CAF50;
        }
        p {
            font-size: 1.2em;
        }
    </style>
</head>
<body>
    <h1>Welcome to Nginx</h1>
    <p>This request was handled by:</p>
    <p><strong id="container-id">Loading...</strong></p>

    <script>
        // Fetch the "X-Container-ID" header
        fetch(window.location.href)
            .then(response => {
                // Vérifie si l'en-tête X-Container-ID est présent
                const containerId = response.headers.get('X-Container-ID');
                if (containerId) {
                    document.getElementById('container-id').textContent = containerId;
                } else {
                    document.getElementById('container-id').textContent = "Header not found!";
                }
            })
            .catch(error => {
                document.getElementById('container-id').textContent = "Error fetching container ID!";
                console.error('Error:', error);
            });
    </script>
</body>
</html>
  • On test, et la on va voir que l’on tombe toujours sur le meme container … C’est « normal », par defaut le navigateur …
  • L’explication : Les Connexions persistantes (Keep-Alive) :
    • Les navigateurs utilisent par défaut des connexions persistantes pour optimiser les performances. Une fois la connexion TCP établie avec un conteneur, elle reste ouverte, et toutes les requêtes suivantes passent par cette même connexion.
    • Cela signifie que le Load Balancer de Docker Swarm ne redistribue pas les requêtes tant que la connexion reste active.​

image

  • Pour vérifier que les requêtes sont bien distribuées entre différents conteneurs, voici un petit script Python à exécuter depuis une machine cliente. Ce script permet de tester le Load Balancer sans utiliser de connexion persistante comme le ferait un navigateur. Il affichera clairement sur quel conteneur chaque requête aboutit.
import requests

# Configuration
host = "http://192.168.1.69:30000"  # Remplacez <IP_HOTE> par l'adresse IP ou le domaine de votre hôte
num_requests = 1000  # Nombre de requêtes à envoyer

responses = []

print(f"Sending {num_requests} requests to {host}\n")

# Envoi des requêtes
for i in range(num_requests):
    try:
        response = requests.get(host, timeout=5)  # Timeout de 5 secondes
        if response.status_code == 200:
            # Récupère l'en-tête X-Container-ID si disponible
            container_id = response.headers.get('X-Container-ID', 'Not Found')
            responses.append(container_id)
            print(f"Request {i + 1}: Container ID -> {container_id}")
        else:
            print(f"Request {i + 1}: Error -> Status Code {response.status_code}")
    except Exception as e:
        print(f"Request {i + 1}: Failed -> {e}")

# Analyse des réponses
unique_responses = set(responses)
print("\nSummary:")
print(f"Total unique container IDs: {len(unique_responses)}")
print(f"Unique container IDs: {unique_responses}")

image

  • Nous voyons bien ainsi que chaque requette est bien distribué sur des containers différents.