Ce projet consiste en une infrastructure web implémentée à l'aide de Docker Compose. Cette infrastructure est composée d'un ou plusieurs serveurs web statique, une API CRUD HTTP avec sa base de donnée Postgresql ainsi qu'un reverse proxy Traefik. Ce qui suit explique comment nous avons construit cette infrastructure dans l'ordre dans laquelle nous l'avons réalisée.
Pour créer notre image docker, nous avons repris la dernière image officielle de nginx et copié notre site web dans le dossier /www et le fichier de configuration nginx.
server{
listen 0.0.0.0:80;
root /www;
location / {
index index.html;
}
}
Notre serveur nginx est configuré pour écouter toutes les connexion sur le port 80 (0.0.0.0:80) et aussi configurer la racine de notre serveur dans le dossier /www (le dossier contenant notre site web). Notre site web ne comporte que une seul page et donc nous avons une seul location avec notre fichier index.html.
Nous avons choisi une template sur startboostrap.com et avons légérement modifié sont contenu pour en faire un site web simple avec quelque boutons et un menu.
Contenu du dockerfile de l'image du site web statique:
FROM nginx:latest
COPY ./site/ /www
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
Dans ce dockerfile on monte le répertoire principal du site et on indique aussi le fichier de configuration de Nginx.
services:
webserver:
image: web/static
ports:
- "8080:80"
Pour l'instant docker compose va démarrer l'image du site web statique et bind le port 80 sur 8080.
Pour faire une API HTTP nous avons développé un petit programme Java utilisant la librairie Javalin. Cette API nous permet de gérer une liste de pays ainsi que leurs informations (capitales et population). L'API propose toutes les opérations CRUD (Create-Read-Update-Delete). Pour correctement utiliser l'API il est conseillé de faire les commandes avec un client API (comme Bruno).
Pour stocker les données, nous avons ajouté une base de données Postgresql ave qui l'API communique afin de modifier ou de consulter les informations.
La base de données contient une unique table country dans le schéma Countries avec les colonnes name, capital et population.
CREATE TABLE country(
name VARCHAR(50),
capital VARCHAR(50),
population INTEGER,
PRIMARY KEY(name)
);
Pour utiliser Postgresql avec docker, il faut, en plus des champs habituels, ajouter des variables d'environement qui serviront à créer un utilisateur avec son mot de passe afin de pouvoir se connecter ainsi que le nom de la base de donnée principale.
db:
build:
context: ./Database
container_name: db
expose:
- "5432"
environment:
- POSTGRES_USER=apiUser
- POSTGRES_PASSWORD=1_L0VE_D@1
- POSTGRES_DB=countryDB
Pour faire un nouveau package Java de cet API nous devons ajouter une dépendance à Javalin et au connecteur Postgresql dans le fichier pom.xml :
<dependencies>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin-bundle</artifactId>
<version>5.6.3</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.1</version>
</dependency>
</dependencies>
Ajout d'un pays :
Suppression d'un pays :
Modification d'un pays :
Affichage d'un pays :
Affichage de tous les pays :
Exemple de résultat avec la commande GET ci dessus :
{
"Switzerland": {
"name": "Switzerland",
"capital": "Lausanne",
"population": 8796669
},
"Germany": {
"name": "Germany",
"capital": "Berlin",
"population": 832994633
},
"India": {
"name": "India",
"capital": "New Delhi",
"population": 1428627663
}
}
traefik:
image: traefik
command:
- --api.insecure=true
- --providers.docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.yaml:/etc/traefik/traefik.yaml
ports:
- "80:80"
- "3141:3141"
- "8080:8080"
Nous utilisons le port par défaut pour le site web, le port 3141 toujours pour l'API et le port 8080 pour le dashboard Traefik. Le fichier traefik.yaml contient la configuation et sera utilisé plus tard pour l'ajout de log et de SSL/TLS.
Note
Il faut monter un volume de Traefik sur le socket Docker pour qu'il ait accès aux containers
webserver:
build:
context: ./StaticWebServer
expose:
- "80"
labels:
- traefik.http.routers.webserver.rule=Host(`localhost`)
La commande .rule=Host('localhost') permet de configurer Traefik pour qu'il redirige cet url vers la serveur web. Donc pour accèder au site web il faut utiliser l'url http://localhost.
api:
build:
context: ./HttpAPI
expose:
- "3141"
labels:
- traefik.http.routers.api.rule=Host(`localhost`) && PathPrefix(`/api/`)
Il faut spécifier un chemin supplémentaire pour que Traefik redirige le chemin localhost/api/ sur le container de l'API.
On ne doit pas spécifier le port pour accéder à l'API (par ex. "localhost:3141") sinon ça n'arrivera pas sur Traefik. On doit seulement utiliser le chemin configuré pour Traefik pour qu'il puisse le reconnaitre et nous rediriger vers l'API.
Important
Il faut modifier les urls d'accès dans l'API car maintenant le chemin de base de l'api est localhost/api et plus juste localhost
Pour que le container Docker Compose accueille plusieurs instances de chaque service, il faut ajouter ce paramètre à chaque service :
deploy:
replicas: 3
On peut aussi ajouter des instances au container avec la commande : docker compose up --scale <service>=nbInstance --no-recreate.
Le petit désavantage de cette commande est qu'elle va bien créer n instance du service mais cela va réinitialiser le nombre d'instances des autres services au nombre spécifié dans le paramètre deploy du fichier compose.yml.
Pour pallier à ce problème il faut spécifier les deux services api et webserver dans la commande :
docker compose up --scale api=3 --scale webserver=4 --no-recreate
Il est aussi possible de d'abbord lancer traefik tout seul avec la commande docker compose up traefik et par la suite lancer le nombre d'instance qu'on veut pour chaques services.
Note
C'est comme ça qu'on peut ajuster dynamiquement le nombre de serveurs gérés par load balancing sans arrêter le container Docker.
La méthode de load balancing qu'utilise Traefik est déjà du round robin, il ne faut rien rajouter dans la configuration. Pour les sticky sessions, nous voulons que les clients communiquent avec l'API sur la même instance à chaque fois. Il nous faut alors activer les sessions persistantes sur Traefik.
Il faut ajouter deux nouvelles lignes dans les labels de l'API :
- traefik.http.services.api.loadbalancer.sticky.cookie=true
- traefik.http.services.api.loadbalancer.sticky.cookie.name=RouteID
La 1ère ligne indique qu'on active les sticky sessions, avec Traefik les sticky sessions sont réalisées par des cookies ajoutés dans les headers HTTP. La 2e ligne indique le nom du cookie et il ne reste plus rien d'autre à faire, Traefik s'occupe de tout.
Afin de prouver que les sticky sessions fonctionnent bien, nous pouvons activer les logs d'accès de Traefik.
Il suffit d'ajouter le paramètre suivant dans le fichier traefik.yaml :
accessLog:
filePath: "/logs/access.log"
Maintenant chaque connection passant par le reverse proxy sera affichée dans ce fichier de log.
Accès sur le site web statique
172.22.0.1 - - [17/Jan/2024:16:48:14 +0000] "GET / HTTP/2.0" 200 9618 "-" "-" 67 "webserver@docker" "http://172.22.0.3:80" 0ms
172.22.0.1 - - [17/Jan/2024:16:49:05 +0000] "GET / HTTP/2.0" 200 9618 "-" "-" 71 "webserver@docker" "http://172.22.0.8:80" 1ms
172.22.0.1 - - [17/Jan/2024:16:49:06 +0000] "GET / HTTP/2.0" 200 9618 "-" "-" 75 "webserver@docker" "http://172.22.0.11:80" 0ms
Comme les sticky sessions ne sont pas activées pour le serveur web, quand on se connecte plusieurs fois sur le site web le reverse proxy va rediriger la requête sur des serveurs différents. L'adresse IP du serveur de destination est différente pour chaque requête.
Accès sur l'API
172.22.0.1 - - [17/Jan/2024:16:45:50 +0000] "GET /api/country HTTP/2.0" 200 262 "-" "-" 24 "api@docker" "http://172.22.0.2:3141" 6ms
172.22.0.1 - - [17/Jan/2024:16:45:51 +0000] "GET /api/country HTTP/2.0" 200 262 "-" "-" 25 "api@docker" "http://172.22.0.2:3141" 2ms
172.22.0.1 - - [17/Jan/2024:16:46:01 +0000] "GET /api/country HTTP/2.0" 200 262 "-" "-" 26 "api@docker" "http://172.22.0.2:3141" 2ms
172.22.0.1 - - [17/Jan/2024:16:46:01 +0000] "GET /api/country HTTP/2.0" 200 262 "-" "-" 27 "api@docker" "http://172.22.0.2:3141" 1ms
Les sticky sessions par cookie sont maintenant activées pour l'API HTTP. Un client est censé converser avec la même instance du serveur HTTP. On remarque dans les logs que l'adresse IP de destination n'a pas changée, donc la requête HTTP a toujours été redirigée vers le même serveur.
Pour sécuriser les connexions à notre infrastructure web, nous avons mis en place TLS afin d'utiliser https. Pour cela, nous avons besoin d'un certificat et d'une clé que nous avons généré grâce à openssl et la commande suivante :
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365
On a ensuite monté un volume contenant les deux fichiers (la clé key.pem et le certificat cert.pem) dans le container de traefik.
- ./certificates:/etc/traefik/certificates
On va devoir ensuite configurer traefik pour activer tls et lui indiquer où se trouvent la clé et le certificat. Pour cela, il faut ajouter au fichier traefik.yaml le contenu suivant :
entryPoints:
http:
address: ":80"
https:
address: ":443"
tls:
certificates:
- certFile: /etc/traefik/certificates/cert.pem
keyFile: /etc/traefik/certificates/key.pem
Il y a donc deux choses à configurer, les entryPoints qui sont les ports sur lesquels on va pouvoir se connecter et tls avec le chemain du certificat et de la clé (précédement monté dans un volume de traefik). Et il ne faut pas oublié d'ajouter également le port 443 au container traefik dans le fichier docker compose (sinon on ne pourra pas l'attteindre depuis l'extérieur).
Désormais, il faut utiliser https:// dans l'url pour se connecter au serveur web static ou à l'api.
Important
Comme le certificat n'est pas fourni par un CA (certificate authority), lors d'une connection le navigateur nous le signalera avec un avertissement mais il faut l'ignorer dans notre cas.
Nous avons décidé d'utiliser une solution existante pour gérer une infrastructure sur Docker.
Nous allons utiliser Portainer.
Portainer est un outil de gestion d'environnements Docker ou Kubernetes, il propose une web app qui permet entre autre de créer, modifier ou supprimer des containers au sein d'un environnement créé avec Docker Compose par exemple.
Portainer est une image Docker qui doit s'exécuter dans une stack différente de l'infrastructure web. Pour cela il faut faire un nouveau fichier Docker compose :
version: "3"
services:
portainer:
image: portainer/portainer-ce:latest
ports:
- 9443:9443
volumes:
- data:/data
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
volumes:
data:
Portainer a besoin d'avoir accès au socket Docker afin qu'il puisse y faire des lectures et des modifications.
Ensuite il suffit simplement de lancer notre infrastructure en ligne de commande avec docker compose up dans le réportoire où se trouve le fichier compose.yml.
Portainer est par défaut accessible depuis le chemin https://localhost:9443.
Note
Avant de commencer à utiliser Portainer, on est invité à créer un compte administrateur Portainer qui est local au container Docker.
Contenu de l'onglet stack :
À ce stade il existe deux stacks dans Docker, une pour l'infrastructure web et une autre pour Portainer seul. En sélectionnant la stack de l'infrastructure web nous pouvons voir la liste des containers qu'elle contient :
Depuis ici on peut effectuer plusieures actions sur les containers, les arrêter, les supprimer ou les modifier.
Depuis la page d'un container on peut sélectionner l'option Duplicate/Edit :
Ce qui nous permet ensuite de créer une copie de ce container si on change le nom.
Tip
Il ne faut pas oublier d'exposer le port correspondant au service sur le container. 3141 pour l'API et 80 pour le web statique

Pour terminer il faut sélectionner Deploy the container et une nouvelle instance de notre serveur sera en ligne et Traefik le reconnaitra directement.