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é
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
Une fois Docker installé nous sommes à 80 Mo de RAM utilisé
- 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
- Interface Portainer : https://192.168.1.69:9443
- Une fois tout le Docker Manager installé nous sommes à 111 Mo de RAM utilisé, ce qui laisse pas mal de marge.
- Nous activons le Docker Swarm avec ce Rpi en tant que Manager
docker swarm init
- Cela a activé automatiquement les fonctionnalitésDocker Swarm dans Portainer
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
- 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) (viaports
dans ledocker-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)
- Les requêtes entrantes sur le port publié (par exemple,
<!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.
- 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}")
- Nous voyons bien ainsi que chaque requette est bien distribué sur des containers différents.